mirror of
https://github.com/bpatrik/pigallery2.git
synced 2025-05-23 22:40:25 +02:00
implementing video converting task
note: braking changes in database and config file
This commit is contained in:
parent
51801026cb
commit
f8a361cb9e
@ -5,7 +5,7 @@ import {LogLevel} from '../common/config/private/IPrivateConfig';
|
|||||||
export const winstonSettings = {
|
export const winstonSettings = {
|
||||||
transports: [
|
transports: [
|
||||||
new winston.transports.Console(<any>{
|
new winston.transports.Console(<any>{
|
||||||
level: LogLevel[Config.Server.log.level],
|
level: LogLevel[Config.Server.Log.level],
|
||||||
handleExceptions: true,
|
handleExceptions: true,
|
||||||
json: false,
|
json: false,
|
||||||
colorize: true,
|
colorize: true,
|
||||||
|
@ -1,12 +1,18 @@
|
|||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
|
import * as fs from 'fs';
|
||||||
import {Config} from '../common/config/private/Config';
|
import {Config} from '../common/config/private/Config';
|
||||||
|
|
||||||
class ProjectPathClass {
|
class ProjectPathClass {
|
||||||
public Root: string;
|
public Root: string;
|
||||||
public ImageFolder: string;
|
public ImageFolder: string;
|
||||||
public ThumbnailFolder: string;
|
public ThumbnailFolder: string;
|
||||||
|
public TranscendedFolder: string;
|
||||||
public FrontendFolder: string;
|
public FrontendFolder: string;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.reset();
|
||||||
|
}
|
||||||
|
|
||||||
isAbsolutePath(pathStr: string) {
|
isAbsolutePath(pathStr: string) {
|
||||||
return path.resolve(pathStr) === path.normalize(pathStr);
|
return path.resolve(pathStr) === path.normalize(pathStr);
|
||||||
}
|
}
|
||||||
@ -19,15 +25,22 @@ class ProjectPathClass {
|
|||||||
return this.isAbsolutePath(pathStr) ? pathStr : path.join(this.Root, pathStr);
|
return this.isAbsolutePath(pathStr) ? pathStr : path.join(this.Root, pathStr);
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor() {
|
getRelativePathToImages(pathStr: string): string {
|
||||||
this.reset();
|
return path.relative(this.ImageFolder, pathStr);
|
||||||
}
|
}
|
||||||
|
|
||||||
reset() {
|
reset() {
|
||||||
this.Root = path.join(__dirname, '/../');
|
this.Root = path.join(__dirname, '/../');
|
||||||
this.ImageFolder = this.getAbsolutePath(Config.Server.imagesFolder);
|
this.ImageFolder = this.getAbsolutePath(Config.Server.imagesFolder);
|
||||||
this.ThumbnailFolder = this.getAbsolutePath(Config.Server.thumbnail.folder);
|
this.ThumbnailFolder = this.getAbsolutePath(Config.Server.Thumbnail.folder);
|
||||||
|
this.TranscendedFolder = path.join(this.ThumbnailFolder, 'tc');
|
||||||
this.FrontendFolder = path.join(this.Root, 'dist');
|
this.FrontendFolder = path.join(this.Root, 'dist');
|
||||||
|
|
||||||
|
// create thumbnail folder if not exist
|
||||||
|
if (!fs.existsSync(this.ThumbnailFolder)) {
|
||||||
|
fs.mkdirSync(this.ThumbnailFolder);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,7 +19,7 @@ export class AdminMWs {
|
|||||||
|
|
||||||
|
|
||||||
public static async loadStatistic(req: Request, res: Response, next: NextFunction) {
|
public static async loadStatistic(req: Request, res: Response, next: NextFunction) {
|
||||||
if (Config.Server.database.type === DatabaseType.memory) {
|
if (Config.Server.Database.type === DatabaseType.memory) {
|
||||||
return next(new ErrorDTO(ErrorCodes.GENERAL_ERROR, 'Statistic is only available for indexed content'));
|
return next(new ErrorDTO(ErrorCodes.GENERAL_ERROR, 'Statistic is only available for indexed content'));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -43,7 +43,7 @@ export class AdminMWs {
|
|||||||
|
|
||||||
|
|
||||||
public static async getDuplicates(req: Request, res: Response, next: NextFunction) {
|
public static async getDuplicates(req: Request, res: Response, next: NextFunction) {
|
||||||
if (Config.Server.database.type === DatabaseType.memory) {
|
if (Config.Server.Database.type === DatabaseType.memory) {
|
||||||
return next(new ErrorDTO(ErrorCodes.GENERAL_ERROR, 'Statistic is only available for indexed content'));
|
return next(new ErrorDTO(ErrorCodes.GENERAL_ERROR, 'Statistic is only available for indexed content'));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -72,10 +72,10 @@ export class AdminMWs {
|
|||||||
if (databaseSettings.type !== DatabaseType.memory) {
|
if (databaseSettings.type !== DatabaseType.memory) {
|
||||||
await SQLConnection.tryConnection(databaseSettings);
|
await SQLConnection.tryConnection(databaseSettings);
|
||||||
}
|
}
|
||||||
Config.Server.database = databaseSettings;
|
Config.Server.Database = databaseSettings;
|
||||||
// only updating explicitly set config (not saving config set by the diagnostics)
|
// only updating explicitly set config (not saving config set by the diagnostics)
|
||||||
const original = Config.original();
|
const original = Config.original();
|
||||||
original.Server.database = databaseSettings;
|
original.Server.Database = databaseSettings;
|
||||||
if (databaseSettings.type === DatabaseType.memory) {
|
if (databaseSettings.type === DatabaseType.memory) {
|
||||||
original.Client.Sharing.enabled = false;
|
original.Client.Sharing.enabled = false;
|
||||||
original.Client.Search.enabled = false;
|
original.Client.Search.enabled = false;
|
||||||
@ -86,7 +86,7 @@ export class AdminMWs {
|
|||||||
Logger.info(LOG_TAG, JSON.stringify(Config, null, '\t'));
|
Logger.info(LOG_TAG, JSON.stringify(Config, null, '\t'));
|
||||||
|
|
||||||
await ObjectManagers.reset();
|
await ObjectManagers.reset();
|
||||||
if (Config.Server.database.type !== DatabaseType.memory) {
|
if (Config.Server.Database.type !== DatabaseType.memory) {
|
||||||
await ObjectManagers.InitSQLManagers();
|
await ObjectManagers.InitSQLManagers();
|
||||||
} else {
|
} else {
|
||||||
await ObjectManagers.InitMemoryManagers();
|
await ObjectManagers.InitMemoryManagers();
|
||||||
@ -318,11 +318,11 @@ export class AdminMWs {
|
|||||||
|
|
||||||
await ConfigDiagnostics.testServerThumbnailConfig(settings.server);
|
await ConfigDiagnostics.testServerThumbnailConfig(settings.server);
|
||||||
await ConfigDiagnostics.testClientThumbnailConfig(settings.client);
|
await ConfigDiagnostics.testClientThumbnailConfig(settings.client);
|
||||||
Config.Server.thumbnail = settings.server;
|
Config.Server.Thumbnail = settings.server;
|
||||||
Config.Client.Thumbnail = settings.client;
|
Config.Client.Thumbnail = settings.client;
|
||||||
// only updating explicitly set config (not saving config set by the diagnostics)
|
// only updating explicitly set config (not saving config set by the diagnostics)
|
||||||
const original = Config.original();
|
const original = Config.original();
|
||||||
original.Server.thumbnail = settings.server;
|
original.Server.Thumbnail = settings.server;
|
||||||
original.Client.Thumbnail = settings.client;
|
original.Client.Thumbnail = settings.client;
|
||||||
original.save();
|
original.save();
|
||||||
ProjectPath.reset();
|
ProjectPath.reset();
|
||||||
@ -398,8 +398,8 @@ export class AdminMWs {
|
|||||||
original.Client.Other.enableOnScrollThumbnailPrioritising = settings.Client.enableOnScrollThumbnailPrioritising;
|
original.Client.Other.enableOnScrollThumbnailPrioritising = settings.Client.enableOnScrollThumbnailPrioritising;
|
||||||
original.Client.Other.defaultPhotoSortingMethod = settings.Client.defaultPhotoSortingMethod;
|
original.Client.Other.defaultPhotoSortingMethod = settings.Client.defaultPhotoSortingMethod;
|
||||||
original.Client.Other.NavBar.showItemCount = settings.Client.NavBar.showItemCount;
|
original.Client.Other.NavBar.showItemCount = settings.Client.NavBar.showItemCount;
|
||||||
original.Server.threading.enable = settings.Server.enable;
|
original.Server.Threading.enable = settings.Server.enable;
|
||||||
original.Server.threading.thumbnailThreads = settings.Server.thumbnailThreads;
|
original.Server.Threading.thumbnailThreads = settings.Server.thumbnailThreads;
|
||||||
original.save();
|
original.save();
|
||||||
await ConfigDiagnostics.runDiagnostics();
|
await ConfigDiagnostics.runDiagnostics();
|
||||||
Logger.info(LOG_TAG, 'new config:');
|
Logger.info(LOG_TAG, 'new config:');
|
||||||
@ -420,11 +420,11 @@ export class AdminMWs {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const settings: IndexingConfig = req.body.settings;
|
const settings: IndexingConfig = req.body.settings;
|
||||||
Config.Server.indexing = settings;
|
Config.Server.Indexing = settings;
|
||||||
|
|
||||||
// only updating explicitly set config (not saving config set by the diagnostics)
|
// only updating explicitly set config (not saving config set by the diagnostics)
|
||||||
const original = Config.original();
|
const original = Config.original();
|
||||||
original.Server.indexing = settings;
|
original.Server.Indexing = settings;
|
||||||
original.save();
|
original.save();
|
||||||
await ConfigDiagnostics.runDiagnostics();
|
await ConfigDiagnostics.runDiagnostics();
|
||||||
Logger.info(LOG_TAG, 'new config:');
|
Logger.info(LOG_TAG, 'new config:');
|
||||||
@ -451,8 +451,8 @@ export class AdminMWs {
|
|||||||
const original = Config.original();
|
const original = Config.original();
|
||||||
await ConfigDiagnostics.testTasksConfig(settings, original);
|
await ConfigDiagnostics.testTasksConfig(settings, original);
|
||||||
|
|
||||||
Config.Server.tasks = settings;
|
Config.Server.Tasks = settings;
|
||||||
original.Server.tasks = settings;
|
original.Server.Tasks = settings;
|
||||||
original.save();
|
original.save();
|
||||||
|
|
||||||
await ConfigDiagnostics.runDiagnostics();
|
await ConfigDiagnostics.runDiagnostics();
|
||||||
|
@ -15,6 +15,7 @@ import {MediaDTO} from '../../common/entities/MediaDTO';
|
|||||||
import {VideoDTO} from '../../common/entities/VideoDTO';
|
import {VideoDTO} from '../../common/entities/VideoDTO';
|
||||||
import {Utils} from '../../common/Utils';
|
import {Utils} from '../../common/Utils';
|
||||||
import {QueryParams} from '../../common/QueryParams';
|
import {QueryParams} from '../../common/QueryParams';
|
||||||
|
import {VideoConverterMWs} from './VideoConverterMWs';
|
||||||
|
|
||||||
|
|
||||||
const LOG_TAG = '[GalleryMWs]';
|
const LOG_TAG = '[GalleryMWs]';
|
||||||
@ -165,7 +166,7 @@ export class GalleryMWs {
|
|||||||
}
|
}
|
||||||
const fullMediaPath = path.join(ProjectPath.ImageFolder, req.params.mediaPath);
|
const fullMediaPath = path.join(ProjectPath.ImageFolder, req.params.mediaPath);
|
||||||
|
|
||||||
// check if thumbnail already exist
|
// check if file exist
|
||||||
if (fs.existsSync(fullMediaPath) === false) {
|
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.params.mediaPath, 'can\'t find file: ' + fullMediaPath));
|
||||||
}
|
}
|
||||||
@ -178,6 +179,32 @@ export class GalleryMWs {
|
|||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static loadBestFitVideo(req: Request, res: Response, next: NextFunction) {
|
||||||
|
if (!(req.params.mediaPath)) {
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
const fullMediaPath = path.join(ProjectPath.ImageFolder, req.params.mediaPath);
|
||||||
|
|
||||||
|
if (fs.statSync(fullMediaPath).isDirectory()) {
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if video exist
|
||||||
|
if (fs.existsSync(fullMediaPath) === false) {
|
||||||
|
return next(new ErrorDTO(ErrorCodes.GENERAL_ERROR, 'no such file:' + req.params.mediaPath, 'can\'t find file: ' + fullMediaPath));
|
||||||
|
}
|
||||||
|
req.resultPipe = fullMediaPath;
|
||||||
|
|
||||||
|
const convertedVideo = VideoConverterMWs.generateConvertedFileName(fullMediaPath);
|
||||||
|
|
||||||
|
// check if transcoded video exist
|
||||||
|
if (fs.existsSync(convertedVideo) === true) {
|
||||||
|
req.resultPipe = convertedVideo;
|
||||||
|
}
|
||||||
|
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public static async search(req: Request, res: Response, next: NextFunction) {
|
public static async search(req: Request, res: Response, next: NextFunction) {
|
||||||
if (Config.Client.Search.enabled === false) {
|
if (Config.Client.Search.enabled === false) {
|
||||||
|
68
backend/middlewares/VideoConverterMWs.ts
Normal file
68
backend/middlewares/VideoConverterMWs.ts
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
import * as path from 'path';
|
||||||
|
import * as os from 'os';
|
||||||
|
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(Math.max(1, os.cpus().length - 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);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -27,9 +27,9 @@ export class ThumbnailGeneratorMWs {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (Config.Server.threading.enable === true) {
|
if (Config.Server.Threading.enable === true) {
|
||||||
if (Config.Server.threading.thumbnailThreads > 0) {
|
if (Config.Server.Threading.thumbnailThreads > 0) {
|
||||||
Config.Client.Thumbnail.concurrentThumbnailGenerations = Config.Server.threading.thumbnailThreads;
|
Config.Client.Thumbnail.concurrentThumbnailGenerations = Config.Server.Threading.thumbnailThreads;
|
||||||
} else {
|
} else {
|
||||||
Config.Client.Thumbnail.concurrentThumbnailGenerations = Math.max(1, os.cpus().length - 1);
|
Config.Client.Thumbnail.concurrentThumbnailGenerations = Math.max(1, os.cpus().length - 1);
|
||||||
}
|
}
|
||||||
@ -37,12 +37,12 @@ export class ThumbnailGeneratorMWs {
|
|||||||
Config.Client.Thumbnail.concurrentThumbnailGenerations = 1;
|
Config.Client.Thumbnail.concurrentThumbnailGenerations = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Config.Server.threading.enable === true &&
|
if (Config.Server.Threading.enable === true &&
|
||||||
Config.Server.thumbnail.processingLibrary === ThumbnailProcessingLib.Jimp) {
|
Config.Server.Thumbnail.processingLibrary === ThumbnailProcessingLib.Jimp) {
|
||||||
this.taskQue = new ThumbnailTH(Config.Client.Thumbnail.concurrentThumbnailGenerations);
|
this.taskQue = new ThumbnailTH(Config.Client.Thumbnail.concurrentThumbnailGenerations);
|
||||||
} else {
|
} else {
|
||||||
this.taskQue = new TaskExecuter(Config.Client.Thumbnail.concurrentThumbnailGenerations,
|
this.taskQue = new TaskExecuter(Config.Client.Thumbnail.concurrentThumbnailGenerations,
|
||||||
(input => ThumbnailWorker.render(input, Config.Server.thumbnail.processingLibrary)));
|
(input => ThumbnailWorker.render(input, Config.Server.Thumbnail.processingLibrary)));
|
||||||
}
|
}
|
||||||
|
|
||||||
this.initDone = true;
|
this.initDone = true;
|
||||||
@ -131,14 +131,10 @@ export class ThumbnailGeneratorMWs {
|
|||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
|
|
||||||
// create thumbnail folder if not exist
|
|
||||||
if (!fs.existsSync(ProjectPath.ThumbnailFolder)) {
|
|
||||||
fs.mkdirSync(ProjectPath.ThumbnailFolder);
|
|
||||||
}
|
|
||||||
|
|
||||||
const margin = {
|
const margin = {
|
||||||
x: Math.round(photo.metadata.faces[0].box.width * (Config.Server.thumbnail.personFaceMargin)),
|
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))
|
y: Math.round(photo.metadata.faces[0].box.height * (Config.Server.Thumbnail.personFaceMargin))
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@ -155,7 +151,7 @@ export class ThumbnailGeneratorMWs {
|
|||||||
width: photo.metadata.faces[0].box.width + margin.x,
|
width: photo.metadata.faces[0].box.width + margin.x,
|
||||||
height: photo.metadata.faces[0].box.height + margin.y
|
height: photo.metadata.faces[0].box.height + margin.y
|
||||||
},
|
},
|
||||||
qualityPriority: Config.Server.thumbnail.qualityPriority
|
qualityPriority: Config.Server.Thumbnail.qualityPriority
|
||||||
};
|
};
|
||||||
input.cut.width = Math.min(input.cut.width, photo.metadata.size.width - input.cut.left);
|
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);
|
input.cut.height = Math.min(input.cut.height, photo.metadata.size.height - input.cut.top);
|
||||||
@ -260,10 +256,6 @@ export class ThumbnailGeneratorMWs {
|
|||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
|
|
||||||
// create thumbnail folder if not exist
|
|
||||||
if (!fs.existsSync(ProjectPath.ThumbnailFolder)) {
|
|
||||||
fs.mkdirSync(ProjectPath.ThumbnailFolder);
|
|
||||||
}
|
|
||||||
|
|
||||||
// run on other thread
|
// run on other thread
|
||||||
const input = <RendererInput>{
|
const input = <RendererInput>{
|
||||||
@ -272,7 +264,7 @@ export class ThumbnailGeneratorMWs {
|
|||||||
size: size,
|
size: size,
|
||||||
thPath: thPath,
|
thPath: thPath,
|
||||||
makeSquare: makeSquare,
|
makeSquare: makeSquare,
|
||||||
qualityPriority: Config.Server.thumbnail.qualityPriority
|
qualityPriority: Config.Server.Thumbnail.qualityPriority
|
||||||
};
|
};
|
||||||
try {
|
try {
|
||||||
await this.taskQue.execute(input);
|
await this.taskQue.execute(input);
|
||||||
|
@ -11,21 +11,22 @@ export class DiskManager {
|
|||||||
static threadPool: DiskManagerTH = null;
|
static threadPool: DiskManagerTH = null;
|
||||||
|
|
||||||
public static init() {
|
public static init() {
|
||||||
if (Config.Server.threading.enable === true) {
|
if (Config.Server.Threading.enable === true) {
|
||||||
DiskManager.threadPool = new DiskManagerTH(1);
|
DiskManager.threadPool = new DiskManagerTH(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async scanDirectory(relativeDirectoryName: string): Promise<DirectoryDTO> {
|
public static async scanDirectory(relativeDirectoryName: string,
|
||||||
|
settings: DiskMangerWorker.DirectoryScanSettings = {}): Promise<DirectoryDTO> {
|
||||||
|
|
||||||
Logger.silly(LOG_TAG, 'scanning directory:', relativeDirectoryName);
|
Logger.silly(LOG_TAG, 'scanning directory:', relativeDirectoryName);
|
||||||
|
|
||||||
|
let directory: DirectoryDTO;
|
||||||
|
|
||||||
let directory: DirectoryDTO = null;
|
if (Config.Server.Threading.enable === true) {
|
||||||
|
directory = await DiskManager.threadPool.execute(relativeDirectoryName, settings);
|
||||||
if (Config.Server.threading.enable === true) {
|
|
||||||
directory = await DiskManager.threadPool.execute(relativeDirectoryName);
|
|
||||||
} else {
|
} else {
|
||||||
directory = await DiskMangerWorker.scanDirectory(relativeDirectoryName);
|
directory = await DiskMangerWorker.scanDirectory(relativeDirectoryName, settings);
|
||||||
}
|
}
|
||||||
const addDirs = (dir: DirectoryDTO) => {
|
const addDirs = (dir: DirectoryDTO) => {
|
||||||
dir.media.forEach((ph) => {
|
dir.media.forEach((ph) => {
|
||||||
|
@ -134,7 +134,7 @@ export class ConfigDiagnostics {
|
|||||||
|
|
||||||
static async testFacesConfig(faces: ClientConfig.FacesConfig, config: IPrivateConfig) {
|
static async testFacesConfig(faces: ClientConfig.FacesConfig, config: IPrivateConfig) {
|
||||||
if (faces.enabled === true) {
|
if (faces.enabled === true) {
|
||||||
if (config.Server.database.type === DatabaseType.memory) {
|
if (config.Server.Database.type === DatabaseType.memory) {
|
||||||
throw new Error('Memory Database do not support faces');
|
throw new Error('Memory Database do not support faces');
|
||||||
}
|
}
|
||||||
if (config.Client.Search.enabled === false) {
|
if (config.Client.Search.enabled === false) {
|
||||||
@ -145,7 +145,7 @@ export class ConfigDiagnostics {
|
|||||||
|
|
||||||
static async testSearchConfig(search: ClientConfig.SearchConfig, config: IPrivateConfig) {
|
static async testSearchConfig(search: ClientConfig.SearchConfig, config: IPrivateConfig) {
|
||||||
if (search.enabled === true &&
|
if (search.enabled === true &&
|
||||||
config.Server.database.type === DatabaseType.memory) {
|
config.Server.Database.type === DatabaseType.memory) {
|
||||||
throw new Error('Memory Database do not support searching');
|
throw new Error('Memory Database do not support searching');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -153,7 +153,7 @@ export class ConfigDiagnostics {
|
|||||||
|
|
||||||
static async testSharingConfig(sharing: ClientConfig.SharingConfig, config: IPrivateConfig) {
|
static async testSharingConfig(sharing: ClientConfig.SharingConfig, config: IPrivateConfig) {
|
||||||
if (sharing.enabled === true &&
|
if (sharing.enabled === true &&
|
||||||
config.Server.database.type === DatabaseType.memory) {
|
config.Server.Database.type === DatabaseType.memory) {
|
||||||
throw new Error('Memory Database do not support sharing');
|
throw new Error('Memory Database do not support sharing');
|
||||||
}
|
}
|
||||||
if (sharing.enabled === true &&
|
if (sharing.enabled === true &&
|
||||||
@ -164,7 +164,7 @@ export class ConfigDiagnostics {
|
|||||||
|
|
||||||
static async testRandomPhotoConfig(sharing: ClientConfig.RandomPhotoConfig, config: IPrivateConfig) {
|
static async testRandomPhotoConfig(sharing: ClientConfig.RandomPhotoConfig, config: IPrivateConfig) {
|
||||||
if (sharing.enabled === true &&
|
if (sharing.enabled === true &&
|
||||||
config.Server.database.type === DatabaseType.memory) {
|
config.Server.Database.type === DatabaseType.memory) {
|
||||||
throw new Error('Memory Database do not support sharing');
|
throw new Error('Memory Database do not support sharing');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -194,9 +194,9 @@ export class ConfigDiagnostics {
|
|||||||
|
|
||||||
static async runDiagnostics() {
|
static async runDiagnostics() {
|
||||||
|
|
||||||
if (Config.Server.database.type !== DatabaseType.memory) {
|
if (Config.Server.Database.type !== DatabaseType.memory) {
|
||||||
try {
|
try {
|
||||||
await ConfigDiagnostics.testDatabase(Config.Server.database);
|
await ConfigDiagnostics.testDatabase(Config.Server.Database);
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
const err: Error = ex;
|
const err: Error = ex;
|
||||||
Logger.warn(LOG_TAG, '[SQL error]', err.toString());
|
Logger.warn(LOG_TAG, '[SQL error]', err.toString());
|
||||||
@ -206,24 +206,24 @@ export class ConfigDiagnostics {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Config.Server.thumbnail.processingLibrary !== ThumbnailProcessingLib.Jimp) {
|
if (Config.Server.Thumbnail.processingLibrary !== ThumbnailProcessingLib.Jimp) {
|
||||||
try {
|
try {
|
||||||
await ConfigDiagnostics.testThumbnailLib(Config.Server.thumbnail.processingLibrary);
|
await ConfigDiagnostics.testThumbnailLib(Config.Server.Thumbnail.processingLibrary);
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
const err: Error = ex;
|
const err: Error = ex;
|
||||||
NotificationManager.warning('Thumbnail hardware acceleration is not possible.' +
|
NotificationManager.warning('Thumbnail hardware acceleration is not possible.' +
|
||||||
' \'' + ThumbnailProcessingLib[Config.Server.thumbnail.processingLibrary] + '\' node module is not found.' +
|
' \'' + ThumbnailProcessingLib[Config.Server.Thumbnail.processingLibrary] + '\' node module is not found.' +
|
||||||
' Falling back temporally to JS based thumbnail generation', err.toString());
|
' 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] module error: ', err.toString());
|
||||||
Logger.warn(LOG_TAG, 'Thumbnail hardware acceleration is not possible.' +
|
Logger.warn(LOG_TAG, 'Thumbnail hardware acceleration is not possible.' +
|
||||||
' \'' + ThumbnailProcessingLib[Config.Server.thumbnail.processingLibrary] + '\' node module is not found.' +
|
' \'' + ThumbnailProcessingLib[Config.Server.Thumbnail.processingLibrary] + '\' node module is not found.' +
|
||||||
' Falling back temporally to JS based thumbnail generation');
|
' Falling back temporally to JS based thumbnail generation');
|
||||||
Config.Server.thumbnail.processingLibrary = ThumbnailProcessingLib.Jimp;
|
Config.Server.Thumbnail.processingLibrary = ThumbnailProcessingLib.Jimp;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await ConfigDiagnostics.testThumbnailFolder(Config.Server.thumbnail.folder);
|
await ConfigDiagnostics.testThumbnailFolder(Config.Server.Thumbnail.folder);
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
const err: Error = ex;
|
const err: Error = ex;
|
||||||
NotificationManager.error('Thumbnail folder error', err.toString());
|
NotificationManager.error('Thumbnail folder error', err.toString());
|
||||||
@ -288,7 +288,7 @@ export class ConfigDiagnostics {
|
|||||||
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await ConfigDiagnostics.testTasksConfig(Config.Server.tasks, Config);
|
await ConfigDiagnostics.testTasksConfig(Config.Server.Tasks, Config);
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
const err: Error = ex;
|
const err: Error = ex;
|
||||||
NotificationManager.warning('Some Tasks are not supported with these settings. Disabling temporally. ' +
|
NotificationManager.warning('Some Tasks are not supported with these settings. Disabling temporally. ' +
|
||||||
|
@ -16,9 +16,9 @@ export class GalleryManager implements IGalleryManager {
|
|||||||
if (knownLastModified && knownLastScanned) {
|
if (knownLastModified && knownLastScanned) {
|
||||||
const stat = fs.statSync(path.join(ProjectPath.ImageFolder, relativeDirectoryName));
|
const stat = fs.statSync(path.join(ProjectPath.ImageFolder, relativeDirectoryName));
|
||||||
const lastModified = DiskMangerWorker.calcLastModified(stat);
|
const lastModified = DiskMangerWorker.calcLastModified(stat);
|
||||||
if (Date.now() - knownLastScanned <= Config.Server.indexing.cachedFolderTimeout &&
|
if (Date.now() - knownLastScanned <= Config.Server.Indexing.cachedFolderTimeout &&
|
||||||
lastModified === knownLastModified &&
|
lastModified === knownLastModified &&
|
||||||
Config.Server.indexing.reIndexingSensitivity < ReIndexingSensitivity.high) {
|
Config.Server.Indexing.reIndexingSensitivity < ReIndexingSensitivity.high) {
|
||||||
return Promise.resolve(null);
|
return Promise.resolve(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -43,11 +43,11 @@ export class GalleryManager implements IGalleryManager, ISQLGalleryManager {
|
|||||||
if (knownLastModified && knownLastScanned
|
if (knownLastModified && knownLastScanned
|
||||||
&& lastModified === knownLastModified &&
|
&& lastModified === knownLastModified &&
|
||||||
dir.lastScanned === knownLastScanned) {
|
dir.lastScanned === knownLastScanned) {
|
||||||
if (Config.Server.indexing.reIndexingSensitivity === ReIndexingSensitivity.low) {
|
if (Config.Server.Indexing.reIndexingSensitivity === ReIndexingSensitivity.low) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
if (Date.now() - dir.lastScanned <= Config.Server.indexing.cachedFolderTimeout &&
|
if (Date.now() - dir.lastScanned <= Config.Server.Indexing.cachedFolderTimeout &&
|
||||||
Config.Server.indexing.reIndexingSensitivity === ReIndexingSensitivity.medium) {
|
Config.Server.Indexing.reIndexingSensitivity === ReIndexingSensitivity.medium) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -61,13 +61,13 @@ export class GalleryManager implements IGalleryManager, ISQLGalleryManager {
|
|||||||
|
|
||||||
|
|
||||||
// not indexed since a while, index it in a lazy manner
|
// not indexed since a while, index it in a lazy manner
|
||||||
if ((Date.now() - dir.lastScanned > Config.Server.indexing.cachedFolderTimeout &&
|
if ((Date.now() - dir.lastScanned > Config.Server.Indexing.cachedFolderTimeout &&
|
||||||
Config.Server.indexing.reIndexingSensitivity >= ReIndexingSensitivity.medium) ||
|
Config.Server.Indexing.reIndexingSensitivity >= ReIndexingSensitivity.medium) ||
|
||||||
Config.Server.indexing.reIndexingSensitivity >= ReIndexingSensitivity.high) {
|
Config.Server.Indexing.reIndexingSensitivity >= ReIndexingSensitivity.high) {
|
||||||
// on the fly reindexing
|
// on the fly reindexing
|
||||||
|
|
||||||
Logger.silly(LOG_TAG, 'lazy reindexing reason: cache timeout: lastScanned: '
|
Logger.silly(LOG_TAG, 'lazy reindexing reason: cache timeout: lastScanned: '
|
||||||
+ (Date.now() - dir.lastScanned) + ' ms ago, cachedFolderTimeout:' + Config.Server.indexing.cachedFolderTimeout);
|
+ (Date.now() - dir.lastScanned) + ' ms ago, cachedFolderTimeout:' + Config.Server.Indexing.cachedFolderTimeout);
|
||||||
ObjectManagers.getInstance().IndexingManager.indexDirectory(relativeDirectoryName).catch((err) => {
|
ObjectManagers.getInstance().IndexingManager.indexDirectory(relativeDirectoryName).catch((err) => {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
});
|
});
|
||||||
@ -133,7 +133,7 @@ export class GalleryManager implements IGalleryManager, ISQLGalleryManager {
|
|||||||
query.andWhere('photo.metadata.size.width <= photo.metadata.size.height');
|
query.andWhere('photo.metadata.size.width <= photo.metadata.size.height');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Config.Server.database.type === DatabaseType.mysql) {
|
if (Config.Server.Database.type === DatabaseType.mysql) {
|
||||||
return await query.groupBy('RAND(), photo.id').limit(1).getOne();
|
return await query.groupBy('RAND(), photo.id').limit(1).getOne();
|
||||||
}
|
}
|
||||||
return await query.groupBy('RANDOM()').limit(1).getOne();
|
return await query.groupBy('RANDOM()').limit(1).getOne();
|
||||||
@ -183,7 +183,7 @@ export class GalleryManager implements IGalleryManager, ISQLGalleryManager {
|
|||||||
'media.name=innerMedia.name AND media.metadata.fileSize = innerMedia.fileSize')
|
'media.name=innerMedia.name AND media.metadata.fileSize = innerMedia.fileSize')
|
||||||
.innerJoinAndSelect('media.directory', 'directory')
|
.innerJoinAndSelect('media.directory', 'directory')
|
||||||
.orderBy('media.name, media.metadata.fileSize')
|
.orderBy('media.name, media.metadata.fileSize')
|
||||||
.limit(Config.Server.duplicates.listingLimit).getMany();
|
.limit(Config.Server.Duplicates.listingLimit).getMany();
|
||||||
|
|
||||||
|
|
||||||
const duplicateParis: DuplicatesDTO[] = [];
|
const duplicateParis: DuplicatesDTO[] = [];
|
||||||
@ -237,7 +237,7 @@ export class GalleryManager implements IGalleryManager, ISQLGalleryManager {
|
|||||||
'media.metadata.creationDate=innerMedia.creationDate AND media.metadata.fileSize = innerMedia.fileSize')
|
'media.metadata.creationDate=innerMedia.creationDate AND media.metadata.fileSize = innerMedia.fileSize')
|
||||||
.innerJoinAndSelect('media.directory', 'directory')
|
.innerJoinAndSelect('media.directory', 'directory')
|
||||||
.orderBy('media.metadata.creationDate, media.metadata.fileSize')
|
.orderBy('media.metadata.creationDate, media.metadata.fileSize')
|
||||||
.limit(Config.Server.duplicates.listingLimit).getMany();
|
.limit(Config.Server.Duplicates.listingLimit).getMany();
|
||||||
|
|
||||||
processDuplicates(duplicates,
|
processDuplicates(duplicates,
|
||||||
(a, b) => a.metadata.creationDate === b.metadata.creationDate &&
|
(a, b) => a.metadata.creationDate === b.metadata.creationDate &&
|
||||||
@ -297,7 +297,7 @@ export class GalleryManager implements IGalleryManager, ISQLGalleryManager {
|
|||||||
dir: dir.directories[i].id
|
dir: dir.directories[i].id
|
||||||
})
|
})
|
||||||
.orderBy('media.metadata.creationDate', 'ASC')
|
.orderBy('media.metadata.creationDate', 'ASC')
|
||||||
.limit(Config.Server.indexing.folderPreviewSize)
|
.limit(Config.Server.Indexing.folderPreviewSize)
|
||||||
.getMany();
|
.getMany();
|
||||||
dir.directories[i].isPartial = true;
|
dir.directories[i].isPartial = true;
|
||||||
|
|
||||||
|
@ -30,7 +30,7 @@ export class SQLConnection {
|
|||||||
|
|
||||||
public static async getConnection(): Promise<Connection> {
|
public static async getConnection(): Promise<Connection> {
|
||||||
if (this.connection == null) {
|
if (this.connection == null) {
|
||||||
const options: any = this.getDriver(Config.Server.database);
|
const options: any = this.getDriver(Config.Server.Database);
|
||||||
// options.name = 'main';
|
// options.name = 'main';
|
||||||
options.entities = [
|
options.entities = [
|
||||||
UserEntity,
|
UserEntity,
|
||||||
@ -45,8 +45,8 @@ export class SQLConnection {
|
|||||||
VersionEntity
|
VersionEntity
|
||||||
];
|
];
|
||||||
options.synchronize = false;
|
options.synchronize = false;
|
||||||
if (Config.Server.log.sqlLevel !== SQLLogLevel.none) {
|
if (Config.Server.Log.sqlLevel !== SQLLogLevel.none) {
|
||||||
options.logging = SQLLogLevel[Config.Server.log.sqlLevel];
|
options.logging = SQLLogLevel[Config.Server.Log.sqlLevel];
|
||||||
}
|
}
|
||||||
|
|
||||||
this.connection = await this.createConnection(options);
|
this.connection = await this.createConnection(options);
|
||||||
@ -75,8 +75,8 @@ export class SQLConnection {
|
|||||||
VersionEntity
|
VersionEntity
|
||||||
];
|
];
|
||||||
options.synchronize = false;
|
options.synchronize = false;
|
||||||
if (Config.Server.log.sqlLevel !== SQLLogLevel.none) {
|
if (Config.Server.Log.sqlLevel !== SQLLogLevel.none) {
|
||||||
options.logging = SQLLogLevel[Config.Server.log.sqlLevel];
|
options.logging = SQLLogLevel[Config.Server.Log.sqlLevel];
|
||||||
}
|
}
|
||||||
const conn = await this.createConnection(options);
|
const conn = await this.createConnection(options);
|
||||||
await SQLConnection.schemeSync(conn);
|
await SQLConnection.schemeSync(conn);
|
||||||
|
@ -42,7 +42,7 @@ export class SharingManager implements ISharingManager {
|
|||||||
path: inSharing.path
|
path: inSharing.path
|
||||||
});
|
});
|
||||||
|
|
||||||
if (sharing.timeStamp < Date.now() - Config.Server.sharing.updateTimeout) {
|
if (sharing.timeStamp < Date.now() - Config.Server.Sharing.updateTimeout) {
|
||||||
throw new Error('Sharing is locked, can\'t update anymore');
|
throw new Error('Sharing is locked, can\'t update anymore');
|
||||||
}
|
}
|
||||||
if (inSharing.password == null) {
|
if (inSharing.password == null) {
|
||||||
|
@ -5,11 +5,11 @@ import {ColumnOptions} from 'typeorm/decorator/options/ColumnOptions';
|
|||||||
export class ColumnCharsetCS implements ColumnOptions {
|
export class ColumnCharsetCS implements ColumnOptions {
|
||||||
|
|
||||||
public get charset(): string {
|
public get charset(): string {
|
||||||
return Config.Server.database.type === DatabaseType.mysql ? 'utf8' : null;
|
return Config.Server.Database.type === DatabaseType.mysql ? 'utf8' : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public get collation(): string {
|
public get collation(): string {
|
||||||
return Config.Server.database.type === DatabaseType.mysql ? 'utf8_bin' : null;
|
return Config.Server.Database.type === DatabaseType.mysql ? 'utf8_bin' : null;
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,9 @@ export class VideoMetadataEntity extends MediaMetadataEntity implements VideoMet
|
|||||||
})
|
})
|
||||||
duration: number;
|
duration: number;
|
||||||
|
|
||||||
|
@Column('int')
|
||||||
|
fps: number;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import {ITaskManager} from '../interfaces/ITaskManager';
|
import {ITaskManager} from '../interfaces/ITaskManager';
|
||||||
import {TaskProgressDTO} from '../../../common/entities/settings/TaskProgressDTO';
|
import {TaskProgressDTO} from '../../../common/entities/settings/TaskProgressDTO';
|
||||||
import {ITask} from './ITask';
|
import {ITask} from './tasks/ITask';
|
||||||
import {TaskRepository} from './TaskRepository';
|
import {TaskRepository} from './TaskRepository';
|
||||||
import {Config} from '../../../common/config/private/Config';
|
import {Config} from '../../../common/config/private/Config';
|
||||||
import {TaskScheduleDTO, TaskTriggerType} from '../../../common/entities/task/TaskScheduleDTO';
|
import {TaskScheduleDTO, TaskTriggerType} from '../../../common/entities/task/TaskScheduleDTO';
|
||||||
@ -20,7 +20,10 @@ export class TaskManager implements ITaskManager {
|
|||||||
const m: { [id: string]: TaskProgressDTO } = {};
|
const m: { [id: string]: TaskProgressDTO } = {};
|
||||||
TaskRepository.Instance.getAvailableTasks()
|
TaskRepository.Instance.getAvailableTasks()
|
||||||
.filter(t => t.Progress)
|
.filter(t => t.Progress)
|
||||||
.forEach(t => m[t.Name] = t.Progress);
|
.forEach(t => {
|
||||||
|
t.Progress.time.current = Date.now();
|
||||||
|
m[t.Name] = t.Progress;
|
||||||
|
});
|
||||||
return m;
|
return m;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -57,7 +60,7 @@ export class TaskManager implements ITaskManager {
|
|||||||
public runSchedules(): void {
|
public runSchedules(): void {
|
||||||
this.stopSchedules();
|
this.stopSchedules();
|
||||||
Logger.info(LOG_TAG, 'Running task schedules');
|
Logger.info(LOG_TAG, 'Running task schedules');
|
||||||
Config.Server.tasks.scheduled.forEach(s => this.runSchedule(s));
|
Config.Server.Tasks.scheduled.forEach(s => this.runSchedule(s));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected getNextDayOfTheWeek(refDate: Date, dayOfWeek: number) {
|
protected getNextDayOfTheWeek(refDate: Date, dayOfWeek: number) {
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import {ITask} from './ITask';
|
import {ITask} from './tasks/ITask';
|
||||||
import {IndexingTask} from './IndexingTask';
|
import {IndexingTask} from './tasks/IndexingTask';
|
||||||
import {DBRestTask} from './DBResetTask';
|
import {DBRestTask} from './tasks/DBResetTask';
|
||||||
|
import {VideoConvertingTask} from './tasks/VideoConvertingTask';
|
||||||
|
|
||||||
export class TaskRepository {
|
export class TaskRepository {
|
||||||
|
|
||||||
@ -26,3 +27,4 @@ export class TaskRepository {
|
|||||||
|
|
||||||
TaskRepository.Instance.register(new IndexingTask());
|
TaskRepository.Instance.register(new IndexingTask());
|
||||||
TaskRepository.Instance.register(new DBRestTask());
|
TaskRepository.Instance.register(new DBRestTask());
|
||||||
|
TaskRepository.Instance.register(new VideoConvertingTask());
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import {TaskProgressDTO} from '../../../common/entities/settings/TaskProgressDTO';
|
import {TaskProgressDTO} from '../../../../common/entities/settings/TaskProgressDTO';
|
||||||
import {ObjectManagers} from '../ObjectManagers';
|
import {ObjectManagers} from '../../ObjectManagers';
|
||||||
import {Config} from '../../../common/config/private/Config';
|
import {Config} from '../../../../common/config/private/Config';
|
||||||
import {DatabaseType} from '../../../common/config/private/IPrivateConfig';
|
import {DatabaseType} from '../../../../common/config/private/IPrivateConfig';
|
||||||
import {ConfigTemplateEntry, DefaultsTasks} from '../../../common/entities/task/TaskDTO';
|
import {ConfigTemplateEntry, DefaultsTasks} from '../../../../common/entities/task/TaskDTO';
|
||||||
import {Task} from './Task';
|
import {Task} from './Task';
|
||||||
|
|
||||||
const LOG_TAG = '[DBRestTask]';
|
const LOG_TAG = '[DBRestTask]';
|
||||||
@ -12,7 +12,7 @@ export class DBRestTask extends Task {
|
|||||||
public readonly ConfigTemplate: ConfigTemplateEntry[] = null;
|
public readonly ConfigTemplate: ConfigTemplateEntry[] = null;
|
||||||
|
|
||||||
public get Supported(): boolean {
|
public get Supported(): boolean {
|
||||||
return Config.Server.database.type !== DatabaseType.memory;
|
return Config.Server.Database.type !== DatabaseType.memory;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async init() {
|
protected async init() {
|
@ -1,5 +1,5 @@
|
|||||||
import {TaskProgressDTO} from '../../../common/entities/settings/TaskProgressDTO';
|
import {TaskProgressDTO} from '../../../../common/entities/settings/TaskProgressDTO';
|
||||||
import {TaskDTO} from '../../../common/entities/task/TaskDTO';
|
import {TaskDTO} from '../../../../common/entities/task/TaskDTO';
|
||||||
|
|
||||||
export interface ITask<T> extends TaskDTO {
|
export interface ITask<T> extends TaskDTO {
|
||||||
Name: string;
|
Name: string;
|
@ -1,16 +1,16 @@
|
|||||||
import {TaskProgressDTO} from '../../../common/entities/settings/TaskProgressDTO';
|
import {TaskProgressDTO} from '../../../../common/entities/settings/TaskProgressDTO';
|
||||||
import {ObjectManagers} from '../ObjectManagers';
|
import {ObjectManagers} from '../../ObjectManagers';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import {Logger} from '../../Logger';
|
import {Logger} from '../../../Logger';
|
||||||
import {RendererInput, ThumbnailSourceType, ThumbnailWorker} from '../threading/ThumbnailWorker';
|
import {RendererInput, ThumbnailSourceType, ThumbnailWorker} from '../../threading/ThumbnailWorker';
|
||||||
import {Config} from '../../../common/config/private/Config';
|
import {Config} from '../../../../common/config/private/Config';
|
||||||
import {MediaDTO} from '../../../common/entities/MediaDTO';
|
import {MediaDTO} from '../../../../common/entities/MediaDTO';
|
||||||
import {ProjectPath} from '../../ProjectPath';
|
import {ProjectPath} from '../../../ProjectPath';
|
||||||
import {ThumbnailGeneratorMWs} from '../../middlewares/thumbnail/ThumbnailGeneratorMWs';
|
import {ThumbnailGeneratorMWs} from '../../../middlewares/thumbnail/ThumbnailGeneratorMWs';
|
||||||
import {Task} from './Task';
|
import {Task} from './Task';
|
||||||
import {DatabaseType} from '../../../common/config/private/IPrivateConfig';
|
import {DatabaseType} from '../../../../common/config/private/IPrivateConfig';
|
||||||
import {ConfigTemplateEntry, DefaultsTasks} from '../../../common/entities/task/TaskDTO';
|
import {ConfigTemplateEntry, DefaultsTasks} from '../../../../common/entities/task/TaskDTO';
|
||||||
|
|
||||||
declare const global: any;
|
declare const global: any;
|
||||||
const LOG_TAG = '[IndexingTask]';
|
const LOG_TAG = '[IndexingTask]';
|
||||||
@ -26,7 +26,7 @@ export class IndexingTask extends Task<{ createThumbnails: boolean }> {
|
|||||||
}];
|
}];
|
||||||
|
|
||||||
public get Supported(): boolean {
|
public get Supported(): boolean {
|
||||||
return Config.Server.database.type !== DatabaseType.memory;
|
return Config.Server.Database.type !== DatabaseType.memory;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async init() {
|
protected async init() {
|
||||||
@ -68,8 +68,8 @@ export class IndexingTask extends Task<{ createThumbnails: boolean }> {
|
|||||||
size: Config.Client.Thumbnail.thumbnailSizes[0],
|
size: Config.Client.Thumbnail.thumbnailSizes[0],
|
||||||
thPath: thPath,
|
thPath: thPath,
|
||||||
makeSquare: false,
|
makeSquare: false,
|
||||||
qualityPriority: Config.Server.thumbnail.qualityPriority
|
qualityPriority: Config.Server.Thumbnail.qualityPriority
|
||||||
}, Config.Server.thumbnail.processingLibrary);
|
}, Config.Server.Thumbnail.processingLibrary);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
Logger.error(LOG_TAG, 'Error during indexing job: ' + e.toString());
|
Logger.error(LOG_TAG, 'Error during indexing job: ' + e.toString());
|
@ -1,7 +1,7 @@
|
|||||||
import {TaskProgressDTO} from '../../../common/entities/settings/TaskProgressDTO';
|
import {TaskProgressDTO} from '../../../../common/entities/settings/TaskProgressDTO';
|
||||||
import {Logger} from '../../Logger';
|
import {Logger} from '../../../Logger';
|
||||||
import {ITask} from './ITask';
|
import {ITask} from './ITask';
|
||||||
import {ConfigTemplateEntry} from '../../../common/entities/task/TaskDTO';
|
import {ConfigTemplateEntry} from '../../../../common/entities/task/TaskDTO';
|
||||||
|
|
||||||
declare const process: any;
|
declare const process: any;
|
||||||
|
|
64
backend/model/tasks/tasks/VideoConvertingTask.ts
Normal file
64
backend/model/tasks/tasks/VideoConvertingTask.ts
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
import {TaskProgressDTO} 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 {ProjectPath} from '../../../ProjectPath';
|
||||||
|
import {MediaDTO} from '../../../../common/entities/MediaDTO';
|
||||||
|
import {Logger} from '../../../Logger';
|
||||||
|
import * as path from 'path';
|
||||||
|
import {DiskManager} from '../../DiskManger';
|
||||||
|
import {VideoConverterMWs} from '../../../middlewares/VideoConverterMWs';
|
||||||
|
import {VideoDTO} from '../../../../common/entities/VideoDTO';
|
||||||
|
|
||||||
|
const LOG_TAG = '[VideoConvertingTask]';
|
||||||
|
|
||||||
|
export class VideoConvertingTask extends Task {
|
||||||
|
public readonly Name = DefaultsTasks[DefaultsTasks['Video Converting']];
|
||||||
|
public readonly ConfigTemplate: ConfigTemplateEntry[] = null;
|
||||||
|
queue: (string | VideoDTO)[] = [];
|
||||||
|
|
||||||
|
public get Supported(): boolean {
|
||||||
|
return Config.Client.Video.enabled === true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async init() {
|
||||||
|
this.queue.push('/');
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async step(): Promise<TaskProgressDTO> {
|
||||||
|
if (this.queue.length === 0) {
|
||||||
|
if (global.gc) {
|
||||||
|
global.gc();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (this.running === false) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const entry = this.queue.shift();
|
||||||
|
this.progress.left = this.queue.length;
|
||||||
|
this.progress.progress++;
|
||||||
|
this.progress.time.current = Date.now();
|
||||||
|
if (typeof entry === 'string') {
|
||||||
|
const directory = entry;
|
||||||
|
this.progress.comment = 'scanning directory: ' + entry;
|
||||||
|
const scanned = await DiskManager.scanDirectory(directory, {noPhoto: true, noMetaFile: true});
|
||||||
|
for (let i = 0; i < scanned.directories.length; i++) {
|
||||||
|
this.queue.push(path.join(scanned.directories[i].path, scanned.directories[i].name));
|
||||||
|
}
|
||||||
|
this.queue = this.queue.concat(<VideoDTO[]>scanned.media.filter(m => MediaDTO.isVideo(m)));
|
||||||
|
} else {
|
||||||
|
const video: VideoDTO = entry;
|
||||||
|
const videoPath = path.join(ProjectPath.ImageFolder, video.directory.path, video.directory.name, video.name);
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this.progress;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -12,6 +12,7 @@ import {Logger} from '../../Logger';
|
|||||||
|
|
||||||
const LOG_TAG = '[DiskManagerTask]';
|
const LOG_TAG = '[DiskManagerTask]';
|
||||||
|
|
||||||
|
|
||||||
export class DiskMangerWorker {
|
export class DiskMangerWorker {
|
||||||
|
|
||||||
private static readonly SupportedEXT = {
|
private static readonly SupportedEXT = {
|
||||||
@ -61,8 +62,8 @@ export class DiskMangerWorker {
|
|||||||
const absoluteName = path.normalize(path.join(absoluteDirectoryName, name));
|
const absoluteName = path.normalize(path.join(absoluteDirectoryName, name));
|
||||||
const relativeName = path.normalize(path.join(relativeDirectoryName, name));
|
const relativeName = path.normalize(path.join(relativeDirectoryName, name));
|
||||||
|
|
||||||
for (let j = 0; j < Config.Server.indexing.excludeFolderList.length; j++) {
|
for (let j = 0; j < Config.Server.Indexing.excludeFolderList.length; j++) {
|
||||||
const exclude = Config.Server.indexing.excludeFolderList[j];
|
const exclude = Config.Server.Indexing.excludeFolderList[j];
|
||||||
|
|
||||||
if (exclude.startsWith('/')) {
|
if (exclude.startsWith('/')) {
|
||||||
if (exclude === absoluteName) {
|
if (exclude === absoluteName) {
|
||||||
@ -79,8 +80,8 @@ export class DiskMangerWorker {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// exclude dirs that have the given files (like .ignore)
|
// exclude dirs that have the given files (like .ignore)
|
||||||
for (let j = 0; j < Config.Server.indexing.excludeFileList.length; j++) {
|
for (let j = 0; j < Config.Server.Indexing.excludeFileList.length; j++) {
|
||||||
const exclude = Config.Server.indexing.excludeFileList[j];
|
const exclude = Config.Server.Indexing.excludeFileList[j];
|
||||||
|
|
||||||
if (fs.existsSync(path.join(absoluteName, exclude))) {
|
if (fs.existsSync(path.join(absoluteName, exclude))) {
|
||||||
return true;
|
return true;
|
||||||
@ -90,7 +91,7 @@ export class DiskMangerWorker {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static scanDirectory(relativeDirectoryName: string, maxPhotos: number = null, photosOnly: boolean = false): Promise<DirectoryDTO> {
|
public static scanDirectory(relativeDirectoryName: string, settings: DiskMangerWorker.DirectoryScanSettings = {}): Promise<DirectoryDTO> {
|
||||||
return new Promise<DirectoryDTO>((resolve, reject) => {
|
return new Promise<DirectoryDTO>((resolve, reject) => {
|
||||||
relativeDirectoryName = this.normalizeDirPath(relativeDirectoryName);
|
relativeDirectoryName = this.normalizeDirPath(relativeDirectoryName);
|
||||||
const directoryName = DiskMangerWorker.dirName(relativeDirectoryName);
|
const directoryName = DiskMangerWorker.dirName(relativeDirectoryName);
|
||||||
@ -120,29 +121,36 @@ export class DiskMangerWorker {
|
|||||||
const file = list[i];
|
const file = list[i];
|
||||||
const fullFilePath = path.normalize(path.join(absoluteDirectoryName, file));
|
const fullFilePath = path.normalize(path.join(absoluteDirectoryName, file));
|
||||||
if (fs.statSync(fullFilePath).isDirectory()) {
|
if (fs.statSync(fullFilePath).isDirectory()) {
|
||||||
if (photosOnly === true) {
|
if (settings.noDirectory === true) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (DiskMangerWorker.excludeDir(file, relativeDirectoryName, absoluteDirectoryName)) {
|
if (DiskMangerWorker.excludeDir(file, relativeDirectoryName, absoluteDirectoryName)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// create preview directory
|
||||||
const d = await DiskMangerWorker.scanDirectory(path.join(relativeDirectoryName, file),
|
const d = await DiskMangerWorker.scanDirectory(path.join(relativeDirectoryName, file),
|
||||||
Config.Server.indexing.folderPreviewSize, true
|
{
|
||||||
|
maxPhotos: Config.Server.Indexing.folderPreviewSize,
|
||||||
|
noMetaFile: true,
|
||||||
|
noVideo: true,
|
||||||
|
noDirectory: false
|
||||||
|
}
|
||||||
);
|
);
|
||||||
d.lastScanned = 0; // it was not a fully scan
|
d.lastScanned = 0; // it was not a fully scan
|
||||||
d.isPartial = true;
|
d.isPartial = true;
|
||||||
directory.directories.push(d);
|
directory.directories.push(d);
|
||||||
} else if (DiskMangerWorker.isImage(fullFilePath)) {
|
} else if (!settings.noPhoto && DiskMangerWorker.isImage(fullFilePath)) {
|
||||||
directory.media.push(<PhotoDTO>{
|
directory.media.push(<PhotoDTO>{
|
||||||
name: file,
|
name: file,
|
||||||
directory: null,
|
directory: null,
|
||||||
metadata: await MetadataLoader.loadPhotoMetadata(fullFilePath)
|
metadata: await MetadataLoader.loadPhotoMetadata(fullFilePath)
|
||||||
});
|
});
|
||||||
|
|
||||||
if (maxPhotos != null && directory.media.length > maxPhotos) {
|
if (settings.maxPhotos && directory.media.length > settings.maxPhotos) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} else if (photosOnly === false && Config.Client.Video.enabled === true &&
|
} else if (!settings.noVideo && Config.Client.Video.enabled === true &&
|
||||||
DiskMangerWorker.isVideo(fullFilePath)) {
|
DiskMangerWorker.isVideo(fullFilePath)) {
|
||||||
try {
|
try {
|
||||||
directory.media.push(<VideoDTO>{
|
directory.media.push(<VideoDTO>{
|
||||||
@ -154,7 +162,7 @@ export class DiskMangerWorker {
|
|||||||
Logger.warn('Media loading error, skipping: ' + file + ', reason: ' + e.toString());
|
Logger.warn('Media loading error, skipping: ' + file + ', reason: ' + e.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
} else if (photosOnly === false && Config.Client.MetaFile.enabled === true &&
|
} else if (!settings.noMetaFile && Config.Client.MetaFile.enabled === true &&
|
||||||
DiskMangerWorker.isMetaFile(fullFilePath)) {
|
DiskMangerWorker.isMetaFile(fullFilePath)) {
|
||||||
directory.metaFile.push(<FileDTO>{
|
directory.metaFile.push(<FileDTO>{
|
||||||
name: file,
|
name: file,
|
||||||
@ -192,3 +200,13 @@ export class DiskMangerWorker {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export namespace DiskMangerWorker {
|
||||||
|
export interface DirectoryScanSettings {
|
||||||
|
maxPhotos?: number;
|
||||||
|
noMetaFile?: boolean;
|
||||||
|
noVideo?: boolean;
|
||||||
|
noPhoto?: boolean;
|
||||||
|
noDirectory?: boolean;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -28,7 +28,8 @@ export class MetadataLoader {
|
|||||||
bitRate: 0,
|
bitRate: 0,
|
||||||
duration: 0,
|
duration: 0,
|
||||||
creationDate: 0,
|
creationDate: 0,
|
||||||
fileSize: 0
|
fileSize: 0,
|
||||||
|
fps: 0
|
||||||
};
|
};
|
||||||
try {
|
try {
|
||||||
const stat = fs.statSync(fullPath);
|
const stat = fs.statSync(fullPath);
|
||||||
@ -56,6 +57,9 @@ export class MetadataLoader {
|
|||||||
if (Utils.isInt32(parseInt(data.streams[i].bit_rate, 10))) {
|
if (Utils.isInt32(parseInt(data.streams[i].bit_rate, 10))) {
|
||||||
metadata.bitRate = parseInt(data.streams[i].bit_rate, 10) || null;
|
metadata.bitRate = parseInt(data.streams[i].bit_rate, 10) || null;
|
||||||
}
|
}
|
||||||
|
if (Utils.isInt32(parseInt(data.streams[i].avg_frame_rate, 10))) {
|
||||||
|
metadata.fps = parseInt(data.streams[i].avg_frame_rate, 10) || null;
|
||||||
|
}
|
||||||
metadata.creationDate = Date.parse(data.streams[i].tags.creation_time) || metadata.creationDate;
|
metadata.creationDate = Date.parse(data.streams[i].tags.creation_time) || metadata.creationDate;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ import {RendererInput} from './ThumbnailWorker';
|
|||||||
import {Config} from '../../../common/config/private/Config';
|
import {Config} from '../../../common/config/private/Config';
|
||||||
import {TaskQue, TaskQueEntry} from './TaskQue';
|
import {TaskQue, TaskQueEntry} from './TaskQue';
|
||||||
import {ITaskExecuter} from './TaskExecuter';
|
import {ITaskExecuter} from './TaskExecuter';
|
||||||
|
import {DiskMangerWorker} from './DiskMangerWorker';
|
||||||
|
|
||||||
|
|
||||||
interface WorkerWrapper<O> {
|
interface WorkerWrapper<O> {
|
||||||
@ -26,6 +27,12 @@ export class ThreadPool<O> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected executeTask(task: WorkerTask): Promise<O> {
|
||||||
|
const promise = this.taskQue.add(task).promise.obj;
|
||||||
|
this.run();
|
||||||
|
return promise;
|
||||||
|
}
|
||||||
|
|
||||||
private run = () => {
|
private run = () => {
|
||||||
if (this.taskQue.isEmpty()) {
|
if (this.taskQue.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
@ -40,12 +47,6 @@ export class ThreadPool<O> {
|
|||||||
worker.worker.send(poolTask.data);
|
worker.worker.send(poolTask.data);
|
||||||
};
|
};
|
||||||
|
|
||||||
protected executeTask(task: WorkerTask): Promise<O> {
|
|
||||||
const promise = this.taskQue.add(task).promise.obj;
|
|
||||||
this.run();
|
|
||||||
return promise;
|
|
||||||
}
|
|
||||||
|
|
||||||
private getFreeWorker() {
|
private getFreeWorker() {
|
||||||
for (let i = 0; i < this.workers.length; i++) {
|
for (let i = 0; i < this.workers.length; i++) {
|
||||||
if (this.workers[i].poolTask == null) {
|
if (this.workers[i].poolTask == null) {
|
||||||
@ -88,10 +89,11 @@ export class ThreadPool<O> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class DiskManagerTH extends ThreadPool<DirectoryDTO> implements ITaskExecuter<string, DirectoryDTO> {
|
export class DiskManagerTH extends ThreadPool<DirectoryDTO> implements ITaskExecuter<string, DirectoryDTO> {
|
||||||
execute(relativeDirectoryName: string): Promise<DirectoryDTO> {
|
execute(relativeDirectoryName: string, settings: DiskMangerWorker.DirectoryScanSettings = {}): Promise<DirectoryDTO> {
|
||||||
return super.executeTask(<DiskManagerTask>{
|
return super.executeTask(<DiskManagerTask>{
|
||||||
type: WorkerTaskTypes.diskManager,
|
type: WorkerTaskTypes.diskManager,
|
||||||
relativeDirectoryName: relativeDirectoryName
|
relativeDirectoryName: relativeDirectoryName,
|
||||||
|
settings: settings
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -101,7 +103,7 @@ export class ThumbnailTH extends ThreadPool<void> implements ITaskExecuter<Rende
|
|||||||
return super.executeTask(<ThumbnailTask>{
|
return super.executeTask(<ThumbnailTask>{
|
||||||
type: WorkerTaskTypes.thumbnail,
|
type: WorkerTaskTypes.thumbnail,
|
||||||
input: input,
|
input: input,
|
||||||
renderer: Config.Server.thumbnail.processingLibrary
|
renderer: Config.Server.Thumbnail.processingLibrary
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
69
backend/model/threading/VideoConverterWorker.ts
Normal file
69
backend/model/threading/VideoConverterWorker.ts
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
import {Logger} from '../../Logger';
|
||||||
|
import {FfmpegCommand} from 'fluent-ffmpeg';
|
||||||
|
import {FFmpegFactory} from '../FFmpegFactory';
|
||||||
|
|
||||||
|
|
||||||
|
export interface VideoConverterInput {
|
||||||
|
videoPath: string;
|
||||||
|
output: {
|
||||||
|
path: string,
|
||||||
|
bitRate?: number,
|
||||||
|
resolution?: 240 | 360 | 480 | 720 | 1080 | 1440 | 2160 | 4320,
|
||||||
|
fps?: number,
|
||||||
|
codec: string,
|
||||||
|
format: string
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export class VideoConverterWorker {
|
||||||
|
|
||||||
|
private static ffmpeg = FFmpegFactory.get();
|
||||||
|
|
||||||
|
public static convert(input: VideoConverterInput): Promise<void> {
|
||||||
|
|
||||||
|
if (this.ffmpeg == null) {
|
||||||
|
this.ffmpeg = FFmpegFactory.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
|
||||||
|
Logger.silly('[FFmpeg] transcoding video: ' + input.videoPath);
|
||||||
|
|
||||||
|
|
||||||
|
const command: FfmpegCommand = this.ffmpeg(input.videoPath);
|
||||||
|
let executedCmd = '';
|
||||||
|
command
|
||||||
|
.on('start', (cmd: string) => {
|
||||||
|
Logger.silly('[FFmpeg] running:' + cmd);
|
||||||
|
executedCmd = cmd;
|
||||||
|
})
|
||||||
|
.on('end', () => {
|
||||||
|
resolve();
|
||||||
|
})
|
||||||
|
.on('error', (e: any) => {
|
||||||
|
reject('[FFmpeg] ' + e.toString() + ' executed: ' + executedCmd);
|
||||||
|
});
|
||||||
|
// set video bitrate
|
||||||
|
if (input.output.bitRate) {
|
||||||
|
command.videoBitrate((input.output.bitRate / 1024) + 'k');
|
||||||
|
}
|
||||||
|
// set target codec
|
||||||
|
command.videoCodec(input.output.codec);
|
||||||
|
if (input.output.resolution) {
|
||||||
|
command.size('?x' + input.output.resolution);
|
||||||
|
}
|
||||||
|
|
||||||
|
// set fps
|
||||||
|
if (input.output.fps) {
|
||||||
|
command.fps(input.output.fps);
|
||||||
|
}
|
||||||
|
// set output format to force
|
||||||
|
command.format(input.output.format)
|
||||||
|
// save to file
|
||||||
|
.save(input.output.path);
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -15,7 +15,7 @@ export class Worker {
|
|||||||
let result = null;
|
let result = null;
|
||||||
switch (task.type) {
|
switch (task.type) {
|
||||||
case WorkerTaskTypes.diskManager:
|
case WorkerTaskTypes.diskManager:
|
||||||
result = await DiskMangerWorker.scanDirectory((<DiskManagerTask>task).relativeDirectoryName);
|
result = await DiskMangerWorker.scanDirectory((<DiskManagerTask>task).relativeDirectoryName, (<DiskManagerTask>task).settings);
|
||||||
if (global.gc) {
|
if (global.gc) {
|
||||||
global.gc();
|
global.gc();
|
||||||
}
|
}
|
||||||
@ -48,6 +48,7 @@ export interface WorkerTask {
|
|||||||
|
|
||||||
export interface DiskManagerTask extends WorkerTask {
|
export interface DiskManagerTask extends WorkerTask {
|
||||||
relativeDirectoryName: string;
|
relativeDirectoryName: string;
|
||||||
|
settings: DiskMangerWorker.DirectoryScanSettings;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ThumbnailTask extends WorkerTask {
|
export interface ThumbnailTask extends WorkerTask {
|
||||||
|
@ -16,6 +16,7 @@ export class GalleryRouter {
|
|||||||
this.addGetVideoThumbnail(app);
|
this.addGetVideoThumbnail(app);
|
||||||
this.addGetImage(app);
|
this.addGetImage(app);
|
||||||
this.addGetVideo(app);
|
this.addGetVideo(app);
|
||||||
|
this.addGetBestFitVideo(app);
|
||||||
this.addGetMetaFile(app);
|
this.addGetMetaFile(app);
|
||||||
this.addRandom(app);
|
this.addRandom(app);
|
||||||
this.addDirectoryList(app);
|
this.addDirectoryList(app);
|
||||||
@ -58,6 +59,15 @@ export class GalleryRouter {
|
|||||||
RenderingMWs.renderFile
|
RenderingMWs.renderFile
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
private static addGetBestFitVideo(app: Express) {
|
||||||
|
app.get(['/api/gallery/content/:mediaPath(*\.(mp4|ogg|ogv|webm))/bestFit'],
|
||||||
|
AuthenticationMWs.authenticate,
|
||||||
|
AuthenticationMWs.normalizePathParam('mediaPath'),
|
||||||
|
AuthenticationMWs.authorisePath('mediaPath', false),
|
||||||
|
GalleryMWs.loadBestFitVideo,
|
||||||
|
RenderingMWs.renderFile
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
private static addGetMetaFile(app: Express) {
|
private static addGetMetaFile(app: Express) {
|
||||||
app.get(['/api/gallery/content/:mediaPath(*\.(gpx))'],
|
app.get(['/api/gallery/content/:mediaPath(*\.(gpx))'],
|
||||||
|
@ -82,7 +82,7 @@ export class Server {
|
|||||||
Localizations.init();
|
Localizations.init();
|
||||||
|
|
||||||
this.app.use(locale(Config.Client.languages, 'en'));
|
this.app.use(locale(Config.Client.languages, 'en'));
|
||||||
if (Config.Server.database.type !== DatabaseType.memory) {
|
if (Config.Server.Database.type !== DatabaseType.memory) {
|
||||||
await ObjectManagers.InitSQLManagers();
|
await ObjectManagers.InitSQLManagers();
|
||||||
} else {
|
} else {
|
||||||
await ObjectManagers.InitMemoryManagers();
|
await ObjectManagers.InitMemoryManagers();
|
||||||
|
@ -45,7 +45,7 @@ export class Benchmarks {
|
|||||||
async bmListDirectory(): Promise<BenchmarkResult> {
|
async bmListDirectory(): Promise<BenchmarkResult> {
|
||||||
const gm = new GalleryManager();
|
const gm = new GalleryManager();
|
||||||
await this.setupDB();
|
await this.setupDB();
|
||||||
Config.Server.indexing.reIndexingSensitivity = ReIndexingSensitivity.low;
|
Config.Server.Indexing.reIndexingSensitivity = ReIndexingSensitivity.low;
|
||||||
return await this.benchmark(() => gm.listDirectory('./'));
|
return await this.benchmark(() => gm.listDirectory('./'));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -122,8 +122,8 @@ export class Benchmarks {
|
|||||||
if (fs.existsSync(this.dbPath)) {
|
if (fs.existsSync(this.dbPath)) {
|
||||||
fs.unlinkSync(this.dbPath);
|
fs.unlinkSync(this.dbPath);
|
||||||
}
|
}
|
||||||
Config.Server.database.type = DatabaseType.sqlite;
|
Config.Server.Database.type = DatabaseType.sqlite;
|
||||||
Config.Server.database.sqlite.storage = this.dbPath;
|
Config.Server.Database.sqlite.storage = this.dbPath;
|
||||||
await ObjectManagers.InitSQLManagers();
|
await ObjectManagers.InitSQLManagers();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1 +1 @@
|
|||||||
export const DataStructureVersion = 13;
|
export const DataStructureVersion = 14;
|
||||||
|
@ -6,7 +6,7 @@ export enum DatabaseType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export enum LogLevel {
|
export enum LogLevel {
|
||||||
error = 1, warn = 2, info = 3, verbose = 4, debug = 5, silly = 6
|
error = 1, warn = 2, info = 3, verbose = 4, debug = 5, silly = 6
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum SQLLogLevel {
|
export enum SQLLogLevel {
|
||||||
@ -55,8 +55,8 @@ export interface IndexingConfig {
|
|||||||
folderPreviewSize: number;
|
folderPreviewSize: number;
|
||||||
cachedFolderTimeout: number; // Do not rescans the folder if seems ok
|
cachedFolderTimeout: number; // Do not rescans the folder if seems ok
|
||||||
reIndexingSensitivity: ReIndexingSensitivity;
|
reIndexingSensitivity: ReIndexingSensitivity;
|
||||||
excludeFolderList: string[]
|
excludeFolderList: string[];
|
||||||
excludeFileList: string[]
|
excludeFileList: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ThreadingConfig {
|
export interface ThreadingConfig {
|
||||||
@ -77,20 +77,31 @@ export interface TaskConfig {
|
|||||||
scheduled: TaskScheduleDTO[];
|
scheduled: TaskScheduleDTO[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface VideoConfig {
|
||||||
|
transcoding: {
|
||||||
|
bitRate: number,
|
||||||
|
resolution: 240 | 360 | 480 | 720 | 1080 | 1440 | 2160 | 4320,
|
||||||
|
fps: number,
|
||||||
|
codec: 'libvpx-vp9' | 'libx264' | 'libvpx',
|
||||||
|
format: 'mp4' | 'webm'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export interface ServerConfig {
|
export interface ServerConfig {
|
||||||
port: number;
|
port: number;
|
||||||
host: string;
|
host: string;
|
||||||
imagesFolder: string;
|
imagesFolder: string;
|
||||||
thumbnail: ThumbnailConfig;
|
Thumbnail: ThumbnailConfig;
|
||||||
threading: ThreadingConfig;
|
Threading: ThreadingConfig;
|
||||||
database: DataBaseConfig;
|
Database: DataBaseConfig;
|
||||||
sharing: SharingConfig;
|
Sharing: SharingConfig;
|
||||||
sessionTimeout: number;
|
sessionTimeout: number;
|
||||||
indexing: IndexingConfig;
|
Indexing: IndexingConfig;
|
||||||
photoMetadataSize: number;
|
photoMetadataSize: number;
|
||||||
duplicates: DuplicatesConfig;
|
Duplicates: DuplicatesConfig;
|
||||||
log: LogConfig;
|
Log: LogConfig;
|
||||||
tasks: TaskConfig;
|
Tasks: TaskConfig;
|
||||||
|
Video: VideoConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IPrivateConfig {
|
export interface IPrivateConfig {
|
||||||
|
@ -12,8 +12,6 @@ import * as path from 'path';
|
|||||||
import {ConfigLoader} from 'typeconfig';
|
import {ConfigLoader} from 'typeconfig';
|
||||||
import {Utils} from '../../Utils';
|
import {Utils} from '../../Utils';
|
||||||
import {UserRoles} from '../../entities/UserDTO';
|
import {UserRoles} from '../../entities/UserDTO';
|
||||||
import {TaskScheduleDTO} from '../../entities/task/TaskScheduleDTO';
|
|
||||||
import {Config} from './Config';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This configuration will be only at backend
|
* This configuration will be only at backend
|
||||||
@ -24,19 +22,19 @@ export class PrivateConfigClass extends PublicConfigClass implements IPrivateCon
|
|||||||
port: 80,
|
port: 80,
|
||||||
host: '0.0.0.0',
|
host: '0.0.0.0',
|
||||||
imagesFolder: 'demo/images',
|
imagesFolder: 'demo/images',
|
||||||
thumbnail: {
|
Thumbnail: {
|
||||||
folder: 'demo/TEMP',
|
folder: 'demo/TEMP',
|
||||||
processingLibrary: ThumbnailProcessingLib.sharp,
|
processingLibrary: ThumbnailProcessingLib.sharp,
|
||||||
qualityPriority: true,
|
qualityPriority: true,
|
||||||
personFaceMargin: 0.6
|
personFaceMargin: 0.6
|
||||||
},
|
},
|
||||||
log: {
|
Log: {
|
||||||
level: LogLevel.info,
|
level: LogLevel.info,
|
||||||
sqlLevel: SQLLogLevel.error
|
sqlLevel: SQLLogLevel.error
|
||||||
},
|
},
|
||||||
sessionTimeout: 1000 * 60 * 60 * 24 * 7,
|
sessionTimeout: 1000 * 60 * 60 * 24 * 7,
|
||||||
photoMetadataSize: 512 * 1024,
|
photoMetadataSize: 512 * 1024,
|
||||||
database: {
|
Database: {
|
||||||
type: DatabaseType.sqlite,
|
type: DatabaseType.sqlite,
|
||||||
mysql: {
|
mysql: {
|
||||||
host: '',
|
host: '',
|
||||||
@ -49,31 +47,40 @@ export class PrivateConfigClass extends PublicConfigClass implements IPrivateCon
|
|||||||
storage: 'sqlite.db'
|
storage: 'sqlite.db'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
sharing: {
|
Sharing: {
|
||||||
updateTimeout: 1000 * 60 * 5
|
updateTimeout: 1000 * 60 * 5
|
||||||
},
|
},
|
||||||
threading: {
|
Threading: {
|
||||||
enable: true,
|
enable: true,
|
||||||
thumbnailThreads: 0
|
thumbnailThreads: 0
|
||||||
},
|
},
|
||||||
indexing: {
|
Indexing: {
|
||||||
folderPreviewSize: 2,
|
folderPreviewSize: 2,
|
||||||
cachedFolderTimeout: 1000 * 60 * 60,
|
cachedFolderTimeout: 1000 * 60 * 60,
|
||||||
reIndexingSensitivity: ReIndexingSensitivity.low,
|
reIndexingSensitivity: ReIndexingSensitivity.low,
|
||||||
excludeFolderList: [],
|
excludeFolderList: [],
|
||||||
excludeFileList: []
|
excludeFileList: []
|
||||||
},
|
},
|
||||||
duplicates: {
|
Duplicates: {
|
||||||
listingLimit: 1000
|
listingLimit: 1000
|
||||||
},
|
},
|
||||||
tasks: {
|
Tasks: {
|
||||||
scheduled: []
|
scheduled: []
|
||||||
|
},
|
||||||
|
Video: {
|
||||||
|
transcoding: {
|
||||||
|
bitRate: 5 * 1024 * 1024,
|
||||||
|
codec: 'libx264',
|
||||||
|
format: 'mp4',
|
||||||
|
fps: 25,
|
||||||
|
resolution: 720
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
private ConfigLoader: any;
|
private ConfigLoader: any;
|
||||||
|
|
||||||
public setDatabaseType(type: DatabaseType) {
|
public setDatabaseType(type: DatabaseType) {
|
||||||
this.Server.database.type = type;
|
this.Server.Database.type = type;
|
||||||
if (type === DatabaseType.memory) {
|
if (type === DatabaseType.memory) {
|
||||||
this.Client.Search.enabled = false;
|
this.Client.Search.enabled = false;
|
||||||
this.Client.Sharing.enabled = false;
|
this.Client.Sharing.enabled = false;
|
||||||
@ -94,11 +101,11 @@ export class PrivateConfigClass extends PublicConfigClass implements IPrivateCon
|
|||||||
if (Utils.enumToArray(UserRoles).map(r => r.key).indexOf(this.Client.unAuthenticatedUserRole) === -1) {
|
if (Utils.enumToArray(UserRoles).map(r => r.key).indexOf(this.Client.unAuthenticatedUserRole) === -1) {
|
||||||
throw new Error('Unknown user role for Client.unAuthenticatedUserRole, found: ' + this.Client.unAuthenticatedUserRole);
|
throw new Error('Unknown user role for Client.unAuthenticatedUserRole, found: ' + this.Client.unAuthenticatedUserRole);
|
||||||
}
|
}
|
||||||
if (Utils.enumToArray(LogLevel).map(r => r.key).indexOf(this.Server.log.level) === -1) {
|
if (Utils.enumToArray(LogLevel).map(r => r.key).indexOf(this.Server.Log.level) === -1) {
|
||||||
throw new Error('Unknown Server.log.level, found: ' + this.Server.log.level);
|
throw new Error('Unknown Server.log.level, found: ' + this.Server.Log.level);
|
||||||
}
|
}
|
||||||
if (Utils.enumToArray(SQLLogLevel).map(r => r.key).indexOf(this.Server.log.sqlLevel) === -1) {
|
if (Utils.enumToArray(SQLLogLevel).map(r => r.key).indexOf(this.Server.Log.sqlLevel) === -1) {
|
||||||
throw new Error('Unknown Server.log.level, found: ' + this.Server.log.sqlLevel);
|
throw new Error('Unknown Server.log.level, found: ' + this.Server.Log.sqlLevel);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -15,4 +15,5 @@ export interface VideoMetadata extends MediaMetadata {
|
|||||||
bitRate: number;
|
bitRate: number;
|
||||||
duration: number; // in milliseconds
|
duration: number; // in milliseconds
|
||||||
fileSize: number;
|
fileSize: number;
|
||||||
|
fps: number;
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@ export type fieldType = 'string' | 'number' | 'boolean';
|
|||||||
|
|
||||||
|
|
||||||
export enum DefaultsTasks {
|
export enum DefaultsTasks {
|
||||||
Indexing = 1, 'Database Reset' = 2
|
Indexing = 1, 'Database Reset' = 2, 'Video Converting' = 3
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ConfigTemplateEntry {
|
export interface ConfigTemplateEntry {
|
||||||
|
@ -33,12 +33,19 @@ export class MediaIcon {
|
|||||||
this.media.directory.path, this.media.directory.name, this.media.name, 'icon');
|
this.media.directory.path, this.media.directory.name, this.media.name, 'icon');
|
||||||
}
|
}
|
||||||
|
|
||||||
getPhotoPath() {
|
getMediaPath() {
|
||||||
return Utils.concatUrls(Config.Client.urlBase,
|
return Utils.concatUrls(Config.Client.urlBase,
|
||||||
'/api/gallery/content/',
|
'/api/gallery/content/',
|
||||||
this.media.directory.path, this.media.directory.name, this.media.name);
|
this.media.directory.path, this.media.directory.name, this.media.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getBestFitMediaPath() {
|
||||||
|
return Utils.concatUrls(Config.Client.urlBase,
|
||||||
|
'/api/gallery/content/',
|
||||||
|
this.media.directory.path, this.media.directory.name, this.media.name,
|
||||||
|
'/bestFit');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
equals(other: MediaDTO | MediaIcon): boolean {
|
equals(other: MediaDTO | MediaIcon): boolean {
|
||||||
// is gridphoto
|
// is gridphoto
|
||||||
|
@ -92,6 +92,12 @@
|
|||||||
font-size: 1.5rem;
|
font-size: 1.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.controls-zoom {
|
||||||
|
bottom: 0;
|
||||||
|
z-index: 3;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
.controls-playback {
|
.controls-playback {
|
||||||
padding-right: 15px;
|
padding-right: 15px;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
@ -103,6 +109,7 @@
|
|||||||
padding-right: 15px;
|
padding-right: 15px;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
z-index: 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
.controls-video .oi,
|
.controls-video .oi,
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
<div class="controls controls-top">
|
<div class="controls controls-top">
|
||||||
<a class="highlight control-button"
|
<a class="highlight control-button"
|
||||||
*ngIf="activePhoto"
|
*ngIf="activePhoto"
|
||||||
[href]="activePhoto.gridMedia.getPhotoPath()"
|
[href]="activePhoto.gridMedia.getMediaPath()"
|
||||||
[download]="activePhoto.gridMedia.media.name">
|
[download]="activePhoto.gridMedia.media.name">
|
||||||
<span class="oi oi-data-transfer-download"
|
<span class="oi oi-data-transfer-download"
|
||||||
title="download" i18n-title></span>
|
title="download" i18n-title></span>
|
||||||
@ -55,7 +55,7 @@
|
|||||||
[style.left.px]="photoFrameDim.width/2"
|
[style.left.px]="photoFrameDim.width/2"
|
||||||
[style.width.px]="faceContainerDim.width"
|
[style.width.px]="faceContainerDim.width"
|
||||||
[style.height.px]="faceContainerDim.height"
|
[style.height.px]="faceContainerDim.height"
|
||||||
*ngIf="facesEnabled && activePhoto && zoom == 1">
|
*ngIf="facesEnabled && activePhoto && zoom == 1 && activePhoto.gridMedia.Photo.metadata.faces && activePhoto.gridMedia.Photo.metadata.faces.length > 0">
|
||||||
<a
|
<a
|
||||||
class="face"
|
class="face"
|
||||||
[routerLink]="['/search', face.name, {type: SearchTypes[SearchTypes.person]}]"
|
[routerLink]="['/search', face.name, {type: SearchTypes[SearchTypes.person]}]"
|
||||||
|
@ -50,6 +50,9 @@
|
|||||||
<div class="col-6" *ngIf="VideoData.duration">
|
<div class="col-6" *ngIf="VideoData.duration">
|
||||||
<ng-container i18n>duration</ng-container>
|
<ng-container i18n>duration</ng-container>
|
||||||
: {{VideoData.duration | duration}}</div>
|
: {{VideoData.duration | duration}}</div>
|
||||||
|
<div class="col-6" *ngIf="VideoData.fps">
|
||||||
|
fps: {{VideoData.fps}}/s
|
||||||
|
</div>
|
||||||
<div class="col-6" *ngIf="VideoData.bitRate">
|
<div class="col-6" *ngIf="VideoData.bitRate">
|
||||||
<ng-container i18n>bit rate</ng-container>
|
<ng-container i18n>bit rate</ng-container>
|
||||||
: {{VideoData.bitRate | fileSize}}/s
|
: {{VideoData.bitRate | fileSize}}/s
|
||||||
|
@ -22,7 +22,7 @@
|
|||||||
(error)="onImageError()"
|
(error)="onImageError()"
|
||||||
(timeupdate)="onVideoProgress()"
|
(timeupdate)="onVideoProgress()"
|
||||||
#video>
|
#video>
|
||||||
<source [src]="gridMedia.getPhotoPath()" type="{{getVideoType()}}">
|
<source [src]="gridMedia.getBestFitMediaPath()" type="{{getVideoType()}}">
|
||||||
</video>
|
</video>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
@ -55,7 +55,7 @@ export class GalleryLightboxMediaComponent implements OnChanges {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this.photoSrc == null && this.gridMedia && this.loadMedia) {
|
if (this.photoSrc == null && this.gridMedia && this.loadMedia) {
|
||||||
FixOrientationPipe.transform(this.gridMedia.getPhotoPath(), this.gridMedia.Orientation)
|
FixOrientationPipe.transform(this.gridMedia.getMediaPath(), this.gridMedia.Orientation)
|
||||||
.then((src) => this.photoSrc = src);
|
.then((src) => this.photoSrc = src);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -144,7 +144,7 @@ export class GalleryLightboxMediaComponent implements OnChanges {
|
|||||||
onImageError() {
|
onImageError() {
|
||||||
// TODO:handle error
|
// TODO:handle error
|
||||||
this.imageLoadFinished = true;
|
this.imageLoadFinished = true;
|
||||||
console.error('Error: cannot load media for lightbox url: ' + this.gridMedia.getPhotoPath());
|
console.error('Error: cannot load media for lightbox url: ' + this.gridMedia.getMediaPath());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -165,7 +165,7 @@ export class GalleryLightboxMediaComponent implements OnChanges {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public get PhotoSrc(): string {
|
public get PhotoSrc(): string {
|
||||||
return this.gridMedia.getPhotoPath();
|
return this.gridMedia.getMediaPath();
|
||||||
}
|
}
|
||||||
|
|
||||||
public showThumbnail(): boolean {
|
public showThumbnail(): boolean {
|
||||||
|
@ -25,7 +25,7 @@ export class DatabaseSettingsComponent extends SettingsComponent<DataBaseConfig>
|
|||||||
_settingsService: DatabaseSettingsService,
|
_settingsService: DatabaseSettingsService,
|
||||||
notification: NotificationService,
|
notification: NotificationService,
|
||||||
i18n: I18n) {
|
i18n: I18n) {
|
||||||
super(i18n('Database'), _authService, _navigation, _settingsService, notification, i18n, s => s.Server.database);
|
super(i18n('Database'), _authService, _navigation, _settingsService, notification, i18n, s => s.Server.Database);
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
|
@ -13,7 +13,7 @@ export class FacesSettingsService extends AbstractSettingsService<ClientConfig.F
|
|||||||
}
|
}
|
||||||
|
|
||||||
public isSupported(): boolean {
|
public isSupported(): boolean {
|
||||||
return this._settingsService.settings.value.Server.database.type !== DatabaseType.memory &&
|
return this._settingsService.settings.value.Server.Database.type !== DatabaseType.memory &&
|
||||||
this._settingsService.settings.value.Client.Search.enabled === true;
|
this._settingsService.settings.value.Client.Search.enabled === true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,7 +37,7 @@ export class IndexingSettingsComponent extends SettingsComponent<IndexingConfig,
|
|||||||
<any>_settingsService,
|
<any>_settingsService,
|
||||||
notification,
|
notification,
|
||||||
i18n,
|
i18n,
|
||||||
s => s.Server.indexing);
|
s => s.Server.Indexing);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,7 +41,7 @@ export class IndexingSettingsService extends AbstractSettingsService<IndexingCon
|
|||||||
|
|
||||||
|
|
||||||
public isSupported(): boolean {
|
public isSupported(): boolean {
|
||||||
return this._settingsService.settings.value.Server.database.type !== DatabaseType.memory;
|
return this._settingsService.settings.value.Server.Database.type !== DatabaseType.memory;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -28,7 +28,7 @@ export class OtherSettingsComponent extends SettingsComponent<OtherConfigDTO> im
|
|||||||
notification: NotificationService,
|
notification: NotificationService,
|
||||||
i18n: I18n) {
|
i18n: I18n) {
|
||||||
super(i18n('Other'), _authService, _navigation, _settingsService, notification, i18n, s => ({
|
super(i18n('Other'), _authService, _navigation, _settingsService, notification, i18n, s => ({
|
||||||
Server: s.Server.threading,
|
Server: s.Server.Threading,
|
||||||
Client: s.Client.Other
|
Client: s.Client.Other
|
||||||
}));
|
}));
|
||||||
this.types = Utils.enumToArray(SortingMethods);
|
this.types = Utils.enumToArray(SortingMethods);
|
||||||
|
@ -19,7 +19,7 @@ export class RandomPhotoSettingsService extends AbstractSettingsService<ClientCo
|
|||||||
}
|
}
|
||||||
|
|
||||||
public isSupported(): boolean {
|
public isSupported(): boolean {
|
||||||
return this._settingsService.settings.value.Server.database.type !== DatabaseType.memory;
|
return this._settingsService.settings.value.Server.Database.type !== DatabaseType.memory;
|
||||||
}
|
}
|
||||||
|
|
||||||
public updateSettings(settings: ClientConfig.SharingConfig): Promise<void> {
|
public updateSettings(settings: ClientConfig.SharingConfig): Promise<void> {
|
||||||
|
@ -13,7 +13,7 @@ export class SearchSettingsService extends AbstractSettingsService<ClientConfig.
|
|||||||
}
|
}
|
||||||
|
|
||||||
public isSupported(): boolean {
|
public isSupported(): boolean {
|
||||||
return this._settingsService.settings.value.Server.database.type !== DatabaseType.memory;
|
return this._settingsService.settings.value.Server.Database.type !== DatabaseType.memory;
|
||||||
}
|
}
|
||||||
|
|
||||||
public updateSettings(settings: ClientConfig.SearchConfig): Promise<void> {
|
public updateSettings(settings: ClientConfig.SearchConfig): Promise<void> {
|
||||||
|
@ -82,31 +82,31 @@ export class SettingsService {
|
|||||||
languages: []
|
languages: []
|
||||||
},
|
},
|
||||||
Server: {
|
Server: {
|
||||||
database: {
|
Database: {
|
||||||
type: DatabaseType.memory
|
type: DatabaseType.memory
|
||||||
},
|
},
|
||||||
log: {
|
Log: {
|
||||||
level: LogLevel.info,
|
level: LogLevel.info,
|
||||||
sqlLevel: SQLLogLevel.error
|
sqlLevel: SQLLogLevel.error
|
||||||
},
|
},
|
||||||
sharing: {
|
Sharing: {
|
||||||
updateTimeout: 2000
|
updateTimeout: 2000
|
||||||
},
|
},
|
||||||
imagesFolder: '',
|
imagesFolder: '',
|
||||||
port: 80,
|
port: 80,
|
||||||
host: '0.0.0.0',
|
host: '0.0.0.0',
|
||||||
thumbnail: {
|
Thumbnail: {
|
||||||
personFaceMargin: 0.1,
|
personFaceMargin: 0.1,
|
||||||
folder: '',
|
folder: '',
|
||||||
qualityPriority: true,
|
qualityPriority: true,
|
||||||
processingLibrary: ThumbnailProcessingLib.sharp
|
processingLibrary: ThumbnailProcessingLib.sharp
|
||||||
},
|
},
|
||||||
threading: {
|
Threading: {
|
||||||
enable: true,
|
enable: true,
|
||||||
thumbnailThreads: 0
|
thumbnailThreads: 0
|
||||||
},
|
},
|
||||||
sessionTimeout: 0,
|
sessionTimeout: 0,
|
||||||
indexing: {
|
Indexing: {
|
||||||
cachedFolderTimeout: 0,
|
cachedFolderTimeout: 0,
|
||||||
folderPreviewSize: 0,
|
folderPreviewSize: 0,
|
||||||
reIndexingSensitivity: ReIndexingSensitivity.medium,
|
reIndexingSensitivity: ReIndexingSensitivity.medium,
|
||||||
@ -114,11 +114,20 @@ export class SettingsService {
|
|||||||
excludeFileList: []
|
excludeFileList: []
|
||||||
},
|
},
|
||||||
photoMetadataSize: 512 * 1024,
|
photoMetadataSize: 512 * 1024,
|
||||||
duplicates: {
|
Duplicates: {
|
||||||
listingLimit: 1000
|
listingLimit: 1000
|
||||||
},
|
},
|
||||||
tasks: {
|
Tasks: {
|
||||||
scheduled: []
|
scheduled: []
|
||||||
|
},
|
||||||
|
Video: {
|
||||||
|
transcoding: {
|
||||||
|
bitRate: 5 * 1024 * 1024,
|
||||||
|
codec: 'libx264',
|
||||||
|
format: 'mp4',
|
||||||
|
fps: 25,
|
||||||
|
resolution: 720
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -15,7 +15,7 @@ export class ShareSettingsService extends AbstractSettingsService<ClientConfig.S
|
|||||||
|
|
||||||
|
|
||||||
public isSupported(): boolean {
|
public isSupported(): boolean {
|
||||||
return this._settingsService.settings.value.Server.database.type !== DatabaseType.memory &&
|
return this._settingsService.settings.value.Server.Database.type !== DatabaseType.memory &&
|
||||||
this._settingsService.settings.value.Client.authenticationRequired === true;
|
this._settingsService.settings.value.Client.authenticationRequired === true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,7 +46,7 @@ export class TasksSettingsComponent extends SettingsComponent<TaskConfig, TasksS
|
|||||||
<any>_settingsService,
|
<any>_settingsService,
|
||||||
notification,
|
notification,
|
||||||
i18n,
|
i18n,
|
||||||
s => s.Server.tasks);
|
s => s.Server.Tasks);
|
||||||
|
|
||||||
this.hasAvailableSettings = !this.simplifiedMode;
|
this.hasAvailableSettings = !this.simplifiedMode;
|
||||||
this.taskTriggerType = Utils.enumToArray(TaskTriggerType);
|
this.taskTriggerType = Utils.enumToArray(TaskTriggerType);
|
||||||
|
@ -29,7 +29,7 @@ export class ThumbnailSettingsComponent
|
|||||||
i18n: I18n) {
|
i18n: I18n) {
|
||||||
super(i18n('Thumbnail'), _authService, _navigation, _settingsService, notification, i18n, s => ({
|
super(i18n('Thumbnail'), _authService, _navigation, _settingsService, notification, i18n, s => ({
|
||||||
client: s.Client.Thumbnail,
|
client: s.Client.Thumbnail,
|
||||||
server: s.Server.thumbnail
|
server: s.Server.Thumbnail
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -62,13 +62,13 @@ export class SQLTestHelper {
|
|||||||
private async initSQLite() {
|
private async initSQLite() {
|
||||||
await this.resetSQLite();
|
await this.resetSQLite();
|
||||||
|
|
||||||
Config.Server.database.type = DatabaseType.sqlite;
|
Config.Server.Database.type = DatabaseType.sqlite;
|
||||||
Config.Server.database.sqlite.storage = this.dbPath;
|
Config.Server.Database.sqlite.storage = this.dbPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async initMySQL() {
|
private async initMySQL() {
|
||||||
Config.Server.database.type = DatabaseType.mysql;
|
Config.Server.Database.type = DatabaseType.mysql;
|
||||||
Config.Server.database.mysql.database = 'pigallery2_test';
|
Config.Server.Database.mysql.database = 'pigallery2_test';
|
||||||
|
|
||||||
await this.resetMySQL();
|
await this.resetMySQL();
|
||||||
}
|
}
|
||||||
@ -85,8 +85,8 @@ export class SQLTestHelper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async resetMySQL() {
|
private async resetMySQL() {
|
||||||
Config.Server.database.type = DatabaseType.mysql;
|
Config.Server.Database.type = DatabaseType.mysql;
|
||||||
Config.Server.database.mysql.database = 'pigallery2_test';
|
Config.Server.Database.mysql.database = 'pigallery2_test';
|
||||||
const conn = await SQLConnection.getConnection();
|
const conn = await SQLConnection.getConnection();
|
||||||
await conn.query('DROP DATABASE IF EXISTS ' + conn.options.database);
|
await conn.query('DROP DATABASE IF EXISTS ' + conn.options.database);
|
||||||
await conn.query('CREATE DATABASE IF NOT EXISTS ' + conn.options.database);
|
await conn.query('CREATE DATABASE IF NOT EXISTS ' + conn.options.database);
|
||||||
@ -94,8 +94,8 @@ export class SQLTestHelper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async clearUpMysql() {
|
private async clearUpMysql() {
|
||||||
Config.Server.database.type = DatabaseType.mysql;
|
Config.Server.Database.type = DatabaseType.mysql;
|
||||||
Config.Server.database.mysql.database = 'pigallery2_test';
|
Config.Server.Database.mysql.database = 'pigallery2_test';
|
||||||
const conn = await SQLConnection.getConnection();
|
const conn = await SQLConnection.getConnection();
|
||||||
await conn.query('DROP DATABASE IF EXISTS ' + conn.options.database);
|
await conn.query('DROP DATABASE IF EXISTS ' + conn.options.database);
|
||||||
await SQLConnection.close();
|
await SQLConnection.close();
|
||||||
|
@ -31,8 +31,8 @@ describe('Typeorm integration', () => {
|
|||||||
fs.mkdirSync(tempDir);
|
fs.mkdirSync(tempDir);
|
||||||
}
|
}
|
||||||
|
|
||||||
Config.Server.database.type = DatabaseType.sqlite;
|
Config.Server.Database.type = DatabaseType.sqlite;
|
||||||
Config.Server.database.sqlite.storage = dbPath;
|
Config.Server.Database.sqlite.storage = dbPath;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -457,7 +457,7 @@ describe('IndexingManager', (sqlHelper: SQLTestHelper) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('with re indexing severity low', async () => {
|
it('with re indexing severity low', async () => {
|
||||||
Config.Server.indexing.reIndexingSensitivity = ReIndexingSensitivity.low;
|
Config.Server.Indexing.reIndexingSensitivity = ReIndexingSensitivity.low;
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
fs.statSync = () => ({ctime: new Date(dirTime), mtime: new Date(dirTime)});
|
fs.statSync = () => ({ctime: new Date(dirTime), mtime: new Date(dirTime)});
|
||||||
|
Loading…
x
Reference in New Issue
Block a user