mirror of
https://github.com/xuthus83/pigallery2.git
synced 2025-01-14 14:43:17 +08: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 = {
|
||||
transports: [
|
||||
new winston.transports.Console(<any>{
|
||||
level: LogLevel[Config.Server.log.level],
|
||||
level: LogLevel[Config.Server.Log.level],
|
||||
handleExceptions: true,
|
||||
json: false,
|
||||
colorize: true,
|
||||
|
@ -1,12 +1,18 @@
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs';
|
||||
import {Config} from '../common/config/private/Config';
|
||||
|
||||
class ProjectPathClass {
|
||||
public Root: string;
|
||||
public ImageFolder: string;
|
||||
public ThumbnailFolder: string;
|
||||
public TranscendedFolder: string;
|
||||
public FrontendFolder: string;
|
||||
|
||||
constructor() {
|
||||
this.reset();
|
||||
}
|
||||
|
||||
isAbsolutePath(pathStr: string) {
|
||||
return path.resolve(pathStr) === path.normalize(pathStr);
|
||||
}
|
||||
@ -19,15 +25,22 @@ class ProjectPathClass {
|
||||
return this.isAbsolutePath(pathStr) ? pathStr : path.join(this.Root, pathStr);
|
||||
}
|
||||
|
||||
constructor() {
|
||||
this.reset();
|
||||
getRelativePathToImages(pathStr: string): string {
|
||||
return path.relative(this.ImageFolder, pathStr);
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.Root = path.join(__dirname, '/../');
|
||||
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');
|
||||
|
||||
// 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) {
|
||||
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'));
|
||||
}
|
||||
|
||||
@ -43,7 +43,7 @@ export class AdminMWs {
|
||||
|
||||
|
||||
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'));
|
||||
}
|
||||
|
||||
@ -72,10 +72,10 @@ export class AdminMWs {
|
||||
if (databaseSettings.type !== DatabaseType.memory) {
|
||||
await SQLConnection.tryConnection(databaseSettings);
|
||||
}
|
||||
Config.Server.database = databaseSettings;
|
||||
Config.Server.Database = databaseSettings;
|
||||
// only updating explicitly set config (not saving config set by the diagnostics)
|
||||
const original = Config.original();
|
||||
original.Server.database = databaseSettings;
|
||||
original.Server.Database = databaseSettings;
|
||||
if (databaseSettings.type === DatabaseType.memory) {
|
||||
original.Client.Sharing.enabled = false;
|
||||
original.Client.Search.enabled = false;
|
||||
@ -86,7 +86,7 @@ export class AdminMWs {
|
||||
Logger.info(LOG_TAG, JSON.stringify(Config, null, '\t'));
|
||||
|
||||
await ObjectManagers.reset();
|
||||
if (Config.Server.database.type !== DatabaseType.memory) {
|
||||
if (Config.Server.Database.type !== DatabaseType.memory) {
|
||||
await ObjectManagers.InitSQLManagers();
|
||||
} else {
|
||||
await ObjectManagers.InitMemoryManagers();
|
||||
@ -318,11 +318,11 @@ export class AdminMWs {
|
||||
|
||||
await ConfigDiagnostics.testServerThumbnailConfig(settings.server);
|
||||
await ConfigDiagnostics.testClientThumbnailConfig(settings.client);
|
||||
Config.Server.thumbnail = settings.server;
|
||||
Config.Server.Thumbnail = settings.server;
|
||||
Config.Client.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.Server.Thumbnail = settings.server;
|
||||
original.Client.Thumbnail = settings.client;
|
||||
original.save();
|
||||
ProjectPath.reset();
|
||||
@ -398,8 +398,8 @@ export class AdminMWs {
|
||||
original.Client.Other.enableOnScrollThumbnailPrioritising = settings.Client.enableOnScrollThumbnailPrioritising;
|
||||
original.Client.Other.defaultPhotoSortingMethod = settings.Client.defaultPhotoSortingMethod;
|
||||
original.Client.Other.NavBar.showItemCount = settings.Client.NavBar.showItemCount;
|
||||
original.Server.threading.enable = settings.Server.enable;
|
||||
original.Server.threading.thumbnailThreads = settings.Server.thumbnailThreads;
|
||||
original.Server.Threading.enable = settings.Server.enable;
|
||||
original.Server.Threading.thumbnailThreads = settings.Server.thumbnailThreads;
|
||||
original.save();
|
||||
await ConfigDiagnostics.runDiagnostics();
|
||||
Logger.info(LOG_TAG, 'new config:');
|
||||
@ -420,11 +420,11 @@ export class AdminMWs {
|
||||
|
||||
try {
|
||||
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)
|
||||
const original = Config.original();
|
||||
original.Server.indexing = settings;
|
||||
original.Server.Indexing = settings;
|
||||
original.save();
|
||||
await ConfigDiagnostics.runDiagnostics();
|
||||
Logger.info(LOG_TAG, 'new config:');
|
||||
@ -451,8 +451,8 @@ export class AdminMWs {
|
||||
const original = Config.original();
|
||||
await ConfigDiagnostics.testTasksConfig(settings, original);
|
||||
|
||||
Config.Server.tasks = settings;
|
||||
original.Server.tasks = settings;
|
||||
Config.Server.Tasks = settings;
|
||||
original.Server.Tasks = settings;
|
||||
original.save();
|
||||
|
||||
await ConfigDiagnostics.runDiagnostics();
|
||||
|
@ -15,6 +15,7 @@ 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';
|
||||
|
||||
|
||||
const LOG_TAG = '[GalleryMWs]';
|
||||
@ -165,7 +166,7 @@ export class GalleryMWs {
|
||||
}
|
||||
const fullMediaPath = path.join(ProjectPath.ImageFolder, req.params.mediaPath);
|
||||
|
||||
// check if thumbnail already exist
|
||||
// check if file exist
|
||||
if (fs.existsSync(fullMediaPath) === false) {
|
||||
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();
|
||||
}
|
||||
|
||||
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) {
|
||||
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.thumbnailThreads > 0) {
|
||||
Config.Client.Thumbnail.concurrentThumbnailGenerations = Config.Server.threading.thumbnailThreads;
|
||||
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);
|
||||
}
|
||||
@ -37,12 +37,12 @@ export class ThumbnailGeneratorMWs {
|
||||
Config.Client.Thumbnail.concurrentThumbnailGenerations = 1;
|
||||
}
|
||||
|
||||
if (Config.Server.threading.enable === true &&
|
||||
Config.Server.thumbnail.processingLibrary === ThumbnailProcessingLib.Jimp) {
|
||||
if (Config.Server.Threading.enable === true &&
|
||||
Config.Server.Thumbnail.processingLibrary === 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)));
|
||||
(input => ThumbnailWorker.render(input, Config.Server.Thumbnail.processingLibrary)));
|
||||
}
|
||||
|
||||
this.initDone = true;
|
||||
@ -131,14 +131,10 @@ export class ThumbnailGeneratorMWs {
|
||||
return next();
|
||||
}
|
||||
|
||||
// create thumbnail folder if not exist
|
||||
if (!fs.existsSync(ProjectPath.ThumbnailFolder)) {
|
||||
fs.mkdirSync(ProjectPath.ThumbnailFolder);
|
||||
}
|
||||
|
||||
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))
|
||||
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))
|
||||
};
|
||||
|
||||
|
||||
@ -155,7 +151,7 @@ export class ThumbnailGeneratorMWs {
|
||||
width: photo.metadata.faces[0].box.width + margin.x,
|
||||
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.height = Math.min(input.cut.height, photo.metadata.size.height - input.cut.top);
|
||||
@ -260,10 +256,6 @@ export class ThumbnailGeneratorMWs {
|
||||
return next();
|
||||
}
|
||||
|
||||
// create thumbnail folder if not exist
|
||||
if (!fs.existsSync(ProjectPath.ThumbnailFolder)) {
|
||||
fs.mkdirSync(ProjectPath.ThumbnailFolder);
|
||||
}
|
||||
|
||||
// run on other thread
|
||||
const input = <RendererInput>{
|
||||
@ -272,7 +264,7 @@ export class ThumbnailGeneratorMWs {
|
||||
size: size,
|
||||
thPath: thPath,
|
||||
makeSquare: makeSquare,
|
||||
qualityPriority: Config.Server.thumbnail.qualityPriority
|
||||
qualityPriority: Config.Server.Thumbnail.qualityPriority
|
||||
};
|
||||
try {
|
||||
await this.taskQue.execute(input);
|
||||
|
@ -11,21 +11,22 @@ export class DiskManager {
|
||||
static threadPool: DiskManagerTH = null;
|
||||
|
||||
public static init() {
|
||||
if (Config.Server.threading.enable === true) {
|
||||
if (Config.Server.Threading.enable === true) {
|
||||
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);
|
||||
|
||||
let directory: DirectoryDTO;
|
||||
|
||||
let directory: DirectoryDTO = null;
|
||||
|
||||
if (Config.Server.threading.enable === true) {
|
||||
directory = await DiskManager.threadPool.execute(relativeDirectoryName);
|
||||
if (Config.Server.Threading.enable === true) {
|
||||
directory = await DiskManager.threadPool.execute(relativeDirectoryName, settings);
|
||||
} else {
|
||||
directory = await DiskMangerWorker.scanDirectory(relativeDirectoryName);
|
||||
directory = await DiskMangerWorker.scanDirectory(relativeDirectoryName, settings);
|
||||
}
|
||||
const addDirs = (dir: DirectoryDTO) => {
|
||||
dir.media.forEach((ph) => {
|
||||
|
@ -134,7 +134,7 @@ export class ConfigDiagnostics {
|
||||
|
||||
static async testFacesConfig(faces: ClientConfig.FacesConfig, config: IPrivateConfig) {
|
||||
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');
|
||||
}
|
||||
if (config.Client.Search.enabled === false) {
|
||||
@ -145,7 +145,7 @@ export class ConfigDiagnostics {
|
||||
|
||||
static async testSearchConfig(search: ClientConfig.SearchConfig, config: IPrivateConfig) {
|
||||
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');
|
||||
}
|
||||
}
|
||||
@ -153,7 +153,7 @@ export class ConfigDiagnostics {
|
||||
|
||||
static async testSharingConfig(sharing: ClientConfig.SharingConfig, config: IPrivateConfig) {
|
||||
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');
|
||||
}
|
||||
if (sharing.enabled === true &&
|
||||
@ -164,7 +164,7 @@ export class ConfigDiagnostics {
|
||||
|
||||
static async testRandomPhotoConfig(sharing: ClientConfig.RandomPhotoConfig, config: IPrivateConfig) {
|
||||
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');
|
||||
}
|
||||
}
|
||||
@ -194,9 +194,9 @@ export class ConfigDiagnostics {
|
||||
|
||||
static async runDiagnostics() {
|
||||
|
||||
if (Config.Server.database.type !== DatabaseType.memory) {
|
||||
if (Config.Server.Database.type !== DatabaseType.memory) {
|
||||
try {
|
||||
await ConfigDiagnostics.testDatabase(Config.Server.database);
|
||||
await ConfigDiagnostics.testDatabase(Config.Server.Database);
|
||||
} catch (ex) {
|
||||
const err: Error = ex;
|
||||
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 {
|
||||
await ConfigDiagnostics.testThumbnailLib(Config.Server.thumbnail.processingLibrary);
|
||||
await ConfigDiagnostics.testThumbnailLib(Config.Server.Thumbnail.processingLibrary);
|
||||
} catch (ex) {
|
||||
const err: Error = ex;
|
||||
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());
|
||||
Logger.warn(LOG_TAG, '[Thumbnail hardware acceleration] module error: ', err.toString());
|
||||
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');
|
||||
Config.Server.thumbnail.processingLibrary = ThumbnailProcessingLib.Jimp;
|
||||
Config.Server.Thumbnail.processingLibrary = ThumbnailProcessingLib.Jimp;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
await ConfigDiagnostics.testThumbnailFolder(Config.Server.thumbnail.folder);
|
||||
await ConfigDiagnostics.testThumbnailFolder(Config.Server.Thumbnail.folder);
|
||||
} catch (ex) {
|
||||
const err: Error = ex;
|
||||
NotificationManager.error('Thumbnail folder error', err.toString());
|
||||
@ -288,7 +288,7 @@ export class ConfigDiagnostics {
|
||||
|
||||
|
||||
try {
|
||||
await ConfigDiagnostics.testTasksConfig(Config.Server.tasks, Config);
|
||||
await ConfigDiagnostics.testTasksConfig(Config.Server.Tasks, Config);
|
||||
} catch (ex) {
|
||||
const err: Error = ex;
|
||||
NotificationManager.warning('Some Tasks are not supported with these settings. Disabling temporally. ' +
|
||||
|
@ -16,9 +16,9 @@ export class GalleryManager implements IGalleryManager {
|
||||
if (knownLastModified && knownLastScanned) {
|
||||
const stat = fs.statSync(path.join(ProjectPath.ImageFolder, relativeDirectoryName));
|
||||
const lastModified = DiskMangerWorker.calcLastModified(stat);
|
||||
if (Date.now() - knownLastScanned <= Config.Server.indexing.cachedFolderTimeout &&
|
||||
if (Date.now() - knownLastScanned <= Config.Server.Indexing.cachedFolderTimeout &&
|
||||
lastModified === knownLastModified &&
|
||||
Config.Server.indexing.reIndexingSensitivity < ReIndexingSensitivity.high) {
|
||||
Config.Server.Indexing.reIndexingSensitivity < ReIndexingSensitivity.high) {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
}
|
||||
|
@ -43,11 +43,11 @@ export class GalleryManager implements IGalleryManager, ISQLGalleryManager {
|
||||
if (knownLastModified && knownLastScanned
|
||||
&& lastModified === knownLastModified &&
|
||||
dir.lastScanned === knownLastScanned) {
|
||||
if (Config.Server.indexing.reIndexingSensitivity === ReIndexingSensitivity.low) {
|
||||
if (Config.Server.Indexing.reIndexingSensitivity === ReIndexingSensitivity.low) {
|
||||
return null;
|
||||
}
|
||||
if (Date.now() - dir.lastScanned <= Config.Server.indexing.cachedFolderTimeout &&
|
||||
Config.Server.indexing.reIndexingSensitivity === ReIndexingSensitivity.medium) {
|
||||
if (Date.now() - dir.lastScanned <= Config.Server.Indexing.cachedFolderTimeout &&
|
||||
Config.Server.Indexing.reIndexingSensitivity === ReIndexingSensitivity.medium) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@ -61,13 +61,13 @@ export class GalleryManager implements IGalleryManager, ISQLGalleryManager {
|
||||
|
||||
|
||||
// not indexed since a while, index it in a lazy manner
|
||||
if ((Date.now() - dir.lastScanned > Config.Server.indexing.cachedFolderTimeout &&
|
||||
Config.Server.indexing.reIndexingSensitivity >= ReIndexingSensitivity.medium) ||
|
||||
Config.Server.indexing.reIndexingSensitivity >= ReIndexingSensitivity.high) {
|
||||
if ((Date.now() - dir.lastScanned > Config.Server.Indexing.cachedFolderTimeout &&
|
||||
Config.Server.Indexing.reIndexingSensitivity >= ReIndexingSensitivity.medium) ||
|
||||
Config.Server.Indexing.reIndexingSensitivity >= ReIndexingSensitivity.high) {
|
||||
// on the fly reindexing
|
||||
|
||||
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) => {
|
||||
console.error(err);
|
||||
});
|
||||
@ -133,7 +133,7 @@ export class GalleryManager implements IGalleryManager, ISQLGalleryManager {
|
||||
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('RANDOM()').limit(1).getOne();
|
||||
@ -183,7 +183,7 @@ export class GalleryManager implements IGalleryManager, ISQLGalleryManager {
|
||||
'media.name=innerMedia.name AND media.metadata.fileSize = innerMedia.fileSize')
|
||||
.innerJoinAndSelect('media.directory', 'directory')
|
||||
.orderBy('media.name, media.metadata.fileSize')
|
||||
.limit(Config.Server.duplicates.listingLimit).getMany();
|
||||
.limit(Config.Server.Duplicates.listingLimit).getMany();
|
||||
|
||||
|
||||
const duplicateParis: DuplicatesDTO[] = [];
|
||||
@ -237,7 +237,7 @@ export class GalleryManager implements IGalleryManager, ISQLGalleryManager {
|
||||
'media.metadata.creationDate=innerMedia.creationDate AND media.metadata.fileSize = innerMedia.fileSize')
|
||||
.innerJoinAndSelect('media.directory', 'directory')
|
||||
.orderBy('media.metadata.creationDate, media.metadata.fileSize')
|
||||
.limit(Config.Server.duplicates.listingLimit).getMany();
|
||||
.limit(Config.Server.Duplicates.listingLimit).getMany();
|
||||
|
||||
processDuplicates(duplicates,
|
||||
(a, b) => a.metadata.creationDate === b.metadata.creationDate &&
|
||||
@ -297,7 +297,7 @@ export class GalleryManager implements IGalleryManager, ISQLGalleryManager {
|
||||
dir: dir.directories[i].id
|
||||
})
|
||||
.orderBy('media.metadata.creationDate', 'ASC')
|
||||
.limit(Config.Server.indexing.folderPreviewSize)
|
||||
.limit(Config.Server.Indexing.folderPreviewSize)
|
||||
.getMany();
|
||||
dir.directories[i].isPartial = true;
|
||||
|
||||
|
@ -30,7 +30,7 @@ export class SQLConnection {
|
||||
|
||||
public static async getConnection(): Promise<Connection> {
|
||||
if (this.connection == null) {
|
||||
const options: any = this.getDriver(Config.Server.database);
|
||||
const options: any = this.getDriver(Config.Server.Database);
|
||||
// options.name = 'main';
|
||||
options.entities = [
|
||||
UserEntity,
|
||||
@ -45,8 +45,8 @@ export class SQLConnection {
|
||||
VersionEntity
|
||||
];
|
||||
options.synchronize = false;
|
||||
if (Config.Server.log.sqlLevel !== SQLLogLevel.none) {
|
||||
options.logging = SQLLogLevel[Config.Server.log.sqlLevel];
|
||||
if (Config.Server.Log.sqlLevel !== SQLLogLevel.none) {
|
||||
options.logging = SQLLogLevel[Config.Server.Log.sqlLevel];
|
||||
}
|
||||
|
||||
this.connection = await this.createConnection(options);
|
||||
@ -75,8 +75,8 @@ export class SQLConnection {
|
||||
VersionEntity
|
||||
];
|
||||
options.synchronize = false;
|
||||
if (Config.Server.log.sqlLevel !== SQLLogLevel.none) {
|
||||
options.logging = SQLLogLevel[Config.Server.log.sqlLevel];
|
||||
if (Config.Server.Log.sqlLevel !== SQLLogLevel.none) {
|
||||
options.logging = SQLLogLevel[Config.Server.Log.sqlLevel];
|
||||
}
|
||||
const conn = await this.createConnection(options);
|
||||
await SQLConnection.schemeSync(conn);
|
||||
|
@ -42,7 +42,7 @@ export class SharingManager implements ISharingManager {
|
||||
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');
|
||||
}
|
||||
if (inSharing.password == null) {
|
||||
|
@ -5,11 +5,11 @@ import {ColumnOptions} from 'typeorm/decorator/options/ColumnOptions';
|
||||
export class ColumnCharsetCS implements ColumnOptions {
|
||||
|
||||
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 {
|
||||
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;
|
||||
|
||||
@Column('int')
|
||||
fps: number;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import {ITaskManager} from '../interfaces/ITaskManager';
|
||||
import {TaskProgressDTO} from '../../../common/entities/settings/TaskProgressDTO';
|
||||
import {ITask} from './ITask';
|
||||
import {ITask} from './tasks/ITask';
|
||||
import {TaskRepository} from './TaskRepository';
|
||||
import {Config} from '../../../common/config/private/Config';
|
||||
import {TaskScheduleDTO, TaskTriggerType} from '../../../common/entities/task/TaskScheduleDTO';
|
||||
@ -20,7 +20,10 @@ export class TaskManager implements ITaskManager {
|
||||
const m: { [id: string]: TaskProgressDTO } = {};
|
||||
TaskRepository.Instance.getAvailableTasks()
|
||||
.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;
|
||||
}
|
||||
|
||||
@ -57,7 +60,7 @@ export class TaskManager implements ITaskManager {
|
||||
public runSchedules(): void {
|
||||
this.stopSchedules();
|
||||
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) {
|
||||
|
@ -1,6 +1,7 @@
|
||||
import {ITask} from './ITask';
|
||||
import {IndexingTask} from './IndexingTask';
|
||||
import {DBRestTask} from './DBResetTask';
|
||||
import {ITask} from './tasks/ITask';
|
||||
import {IndexingTask} from './tasks/IndexingTask';
|
||||
import {DBRestTask} from './tasks/DBResetTask';
|
||||
import {VideoConvertingTask} from './tasks/VideoConvertingTask';
|
||||
|
||||
export class TaskRepository {
|
||||
|
||||
@ -26,3 +27,4 @@ export class TaskRepository {
|
||||
|
||||
TaskRepository.Instance.register(new IndexingTask());
|
||||
TaskRepository.Instance.register(new DBRestTask());
|
||||
TaskRepository.Instance.register(new VideoConvertingTask());
|
||||
|
@ -1,8 +1,8 @@
|
||||
import {TaskProgressDTO} from '../../../common/entities/settings/TaskProgressDTO';
|
||||
import {ObjectManagers} from '../ObjectManagers';
|
||||
import {Config} from '../../../common/config/private/Config';
|
||||
import {DatabaseType} from '../../../common/config/private/IPrivateConfig';
|
||||
import {ConfigTemplateEntry, DefaultsTasks} from '../../../common/entities/task/TaskDTO';
|
||||
import {TaskProgressDTO} from '../../../../common/entities/settings/TaskProgressDTO';
|
||||
import {ObjectManagers} from '../../ObjectManagers';
|
||||
import {Config} from '../../../../common/config/private/Config';
|
||||
import {DatabaseType} from '../../../../common/config/private/IPrivateConfig';
|
||||
import {ConfigTemplateEntry, DefaultsTasks} from '../../../../common/entities/task/TaskDTO';
|
||||
import {Task} from './Task';
|
||||
|
||||
const LOG_TAG = '[DBRestTask]';
|
||||
@ -12,7 +12,7 @@ export class DBRestTask extends Task {
|
||||
public readonly ConfigTemplate: ConfigTemplateEntry[] = null;
|
||||
|
||||
public get Supported(): boolean {
|
||||
return Config.Server.database.type !== DatabaseType.memory;
|
||||
return Config.Server.Database.type !== DatabaseType.memory;
|
||||
}
|
||||
|
||||
protected async init() {
|
@ -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 ITask<T> extends TaskDTO {
|
||||
Name: string;
|
@ -1,16 +1,16 @@
|
||||
import {TaskProgressDTO} from '../../../common/entities/settings/TaskProgressDTO';
|
||||
import {ObjectManagers} from '../ObjectManagers';
|
||||
import {TaskProgressDTO} from '../../../../common/entities/settings/TaskProgressDTO';
|
||||
import {ObjectManagers} from '../../ObjectManagers';
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs';
|
||||
import {Logger} from '../../Logger';
|
||||
import {RendererInput, ThumbnailSourceType, ThumbnailWorker} from '../threading/ThumbnailWorker';
|
||||
import {Config} from '../../../common/config/private/Config';
|
||||
import {MediaDTO} from '../../../common/entities/MediaDTO';
|
||||
import {ProjectPath} from '../../ProjectPath';
|
||||
import {ThumbnailGeneratorMWs} from '../../middlewares/thumbnail/ThumbnailGeneratorMWs';
|
||||
import {Logger} from '../../../Logger';
|
||||
import {RendererInput, ThumbnailSourceType, ThumbnailWorker} from '../../threading/ThumbnailWorker';
|
||||
import {Config} from '../../../../common/config/private/Config';
|
||||
import {MediaDTO} from '../../../../common/entities/MediaDTO';
|
||||
import {ProjectPath} from '../../../ProjectPath';
|
||||
import {ThumbnailGeneratorMWs} from '../../../middlewares/thumbnail/ThumbnailGeneratorMWs';
|
||||
import {Task} from './Task';
|
||||
import {DatabaseType} from '../../../common/config/private/IPrivateConfig';
|
||||
import {ConfigTemplateEntry, DefaultsTasks} from '../../../common/entities/task/TaskDTO';
|
||||
import {DatabaseType} from '../../../../common/config/private/IPrivateConfig';
|
||||
import {ConfigTemplateEntry, DefaultsTasks} from '../../../../common/entities/task/TaskDTO';
|
||||
|
||||
declare const global: any;
|
||||
const LOG_TAG = '[IndexingTask]';
|
||||
@ -26,7 +26,7 @@ export class IndexingTask extends Task<{ createThumbnails: boolean }> {
|
||||
}];
|
||||
|
||||
public get Supported(): boolean {
|
||||
return Config.Server.database.type !== DatabaseType.memory;
|
||||
return Config.Server.Database.type !== DatabaseType.memory;
|
||||
}
|
||||
|
||||
protected async init() {
|
||||
@ -68,8 +68,8 @@ export class IndexingTask extends Task<{ createThumbnails: boolean }> {
|
||||
size: Config.Client.Thumbnail.thumbnailSizes[0],
|
||||
thPath: thPath,
|
||||
makeSquare: false,
|
||||
qualityPriority: Config.Server.thumbnail.qualityPriority
|
||||
}, Config.Server.thumbnail.processingLibrary);
|
||||
qualityPriority: Config.Server.Thumbnail.qualityPriority
|
||||
}, Config.Server.Thumbnail.processingLibrary);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
Logger.error(LOG_TAG, 'Error during indexing job: ' + e.toString());
|
@ -1,7 +1,7 @@
|
||||
import {TaskProgressDTO} from '../../../common/entities/settings/TaskProgressDTO';
|
||||
import {Logger} from '../../Logger';
|
||||
import {TaskProgressDTO} from '../../../../common/entities/settings/TaskProgressDTO';
|
||||
import {Logger} from '../../../Logger';
|
||||
import {ITask} from './ITask';
|
||||
import {ConfigTemplateEntry} from '../../../common/entities/task/TaskDTO';
|
||||
import {ConfigTemplateEntry} from '../../../../common/entities/task/TaskDTO';
|
||||
|
||||
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]';
|
||||
|
||||
|
||||
export class DiskMangerWorker {
|
||||
|
||||
private static readonly SupportedEXT = {
|
||||
@ -61,8 +62,8 @@ export class DiskMangerWorker {
|
||||
const absoluteName = path.normalize(path.join(absoluteDirectoryName, name));
|
||||
const relativeName = path.normalize(path.join(relativeDirectoryName, name));
|
||||
|
||||
for (let j = 0; j < Config.Server.indexing.excludeFolderList.length; j++) {
|
||||
const exclude = Config.Server.indexing.excludeFolderList[j];
|
||||
for (let j = 0; j < Config.Server.Indexing.excludeFolderList.length; j++) {
|
||||
const exclude = Config.Server.Indexing.excludeFolderList[j];
|
||||
|
||||
if (exclude.startsWith('/')) {
|
||||
if (exclude === absoluteName) {
|
||||
@ -79,8 +80,8 @@ export class DiskMangerWorker {
|
||||
}
|
||||
}
|
||||
// exclude dirs that have the given files (like .ignore)
|
||||
for (let j = 0; j < Config.Server.indexing.excludeFileList.length; j++) {
|
||||
const exclude = Config.Server.indexing.excludeFileList[j];
|
||||
for (let j = 0; j < Config.Server.Indexing.excludeFileList.length; j++) {
|
||||
const exclude = Config.Server.Indexing.excludeFileList[j];
|
||||
|
||||
if (fs.existsSync(path.join(absoluteName, exclude))) {
|
||||
return true;
|
||||
@ -90,7 +91,7 @@ export class DiskMangerWorker {
|
||||
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) => {
|
||||
relativeDirectoryName = this.normalizeDirPath(relativeDirectoryName);
|
||||
const directoryName = DiskMangerWorker.dirName(relativeDirectoryName);
|
||||
@ -120,29 +121,36 @@ export class DiskMangerWorker {
|
||||
const file = list[i];
|
||||
const fullFilePath = path.normalize(path.join(absoluteDirectoryName, file));
|
||||
if (fs.statSync(fullFilePath).isDirectory()) {
|
||||
if (photosOnly === true) {
|
||||
if (settings.noDirectory === true) {
|
||||
continue;
|
||||
}
|
||||
if (DiskMangerWorker.excludeDir(file, relativeDirectoryName, absoluteDirectoryName)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// create preview directory
|
||||
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.isPartial = true;
|
||||
directory.directories.push(d);
|
||||
} else if (DiskMangerWorker.isImage(fullFilePath)) {
|
||||
} else if (!settings.noPhoto && DiskMangerWorker.isImage(fullFilePath)) {
|
||||
directory.media.push(<PhotoDTO>{
|
||||
name: file,
|
||||
directory: null,
|
||||
metadata: await MetadataLoader.loadPhotoMetadata(fullFilePath)
|
||||
});
|
||||
|
||||
if (maxPhotos != null && directory.media.length > maxPhotos) {
|
||||
if (settings.maxPhotos && directory.media.length > settings.maxPhotos) {
|
||||
break;
|
||||
}
|
||||
} else if (photosOnly === false && Config.Client.Video.enabled === true &&
|
||||
} else if (!settings.noVideo && Config.Client.Video.enabled === true &&
|
||||
DiskMangerWorker.isVideo(fullFilePath)) {
|
||||
try {
|
||||
directory.media.push(<VideoDTO>{
|
||||
@ -154,7 +162,7 @@ export class DiskMangerWorker {
|
||||
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)) {
|
||||
directory.metaFile.push(<FileDTO>{
|
||||
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,
|
||||
duration: 0,
|
||||
creationDate: 0,
|
||||
fileSize: 0
|
||||
fileSize: 0,
|
||||
fps: 0
|
||||
};
|
||||
try {
|
||||
const stat = fs.statSync(fullPath);
|
||||
@ -56,6 +57,9 @@ export class MetadataLoader {
|
||||
if (Utils.isInt32(parseInt(data.streams[i].bit_rate, 10))) {
|
||||
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;
|
||||
break;
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ import {RendererInput} from './ThumbnailWorker';
|
||||
import {Config} from '../../../common/config/private/Config';
|
||||
import {TaskQue, TaskQueEntry} from './TaskQue';
|
||||
import {ITaskExecuter} from './TaskExecuter';
|
||||
import {DiskMangerWorker} from './DiskMangerWorker';
|
||||
|
||||
|
||||
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 = () => {
|
||||
if (this.taskQue.isEmpty()) {
|
||||
return;
|
||||
@ -40,12 +47,6 @@ export class ThreadPool<O> {
|
||||
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() {
|
||||
for (let i = 0; i < this.workers.length; i++) {
|
||||
if (this.workers[i].poolTask == null) {
|
||||
@ -88,10 +89,11 @@ export class ThreadPool<O> {
|
||||
}
|
||||
|
||||
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>{
|
||||
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>{
|
||||
type: WorkerTaskTypes.thumbnail,
|
||||
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;
|
||||
switch (task.type) {
|
||||
case WorkerTaskTypes.diskManager:
|
||||
result = await DiskMangerWorker.scanDirectory((<DiskManagerTask>task).relativeDirectoryName);
|
||||
result = await DiskMangerWorker.scanDirectory((<DiskManagerTask>task).relativeDirectoryName, (<DiskManagerTask>task).settings);
|
||||
if (global.gc) {
|
||||
global.gc();
|
||||
}
|
||||
@ -48,6 +48,7 @@ export interface WorkerTask {
|
||||
|
||||
export interface DiskManagerTask extends WorkerTask {
|
||||
relativeDirectoryName: string;
|
||||
settings: DiskMangerWorker.DirectoryScanSettings;
|
||||
}
|
||||
|
||||
export interface ThumbnailTask extends WorkerTask {
|
||||
|
@ -16,6 +16,7 @@ export class GalleryRouter {
|
||||
this.addGetVideoThumbnail(app);
|
||||
this.addGetImage(app);
|
||||
this.addGetVideo(app);
|
||||
this.addGetBestFitVideo(app);
|
||||
this.addGetMetaFile(app);
|
||||
this.addRandom(app);
|
||||
this.addDirectoryList(app);
|
||||
@ -58,6 +59,15 @@ export class GalleryRouter {
|
||||
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) {
|
||||
app.get(['/api/gallery/content/:mediaPath(*\.(gpx))'],
|
||||
|
@ -82,7 +82,7 @@ export class Server {
|
||||
Localizations.init();
|
||||
|
||||
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();
|
||||
} else {
|
||||
await ObjectManagers.InitMemoryManagers();
|
||||
|
@ -45,7 +45,7 @@ export class Benchmarks {
|
||||
async bmListDirectory(): Promise<BenchmarkResult> {
|
||||
const gm = new GalleryManager();
|
||||
await this.setupDB();
|
||||
Config.Server.indexing.reIndexingSensitivity = ReIndexingSensitivity.low;
|
||||
Config.Server.Indexing.reIndexingSensitivity = ReIndexingSensitivity.low;
|
||||
return await this.benchmark(() => gm.listDirectory('./'));
|
||||
}
|
||||
|
||||
@ -122,8 +122,8 @@ export class Benchmarks {
|
||||
if (fs.existsSync(this.dbPath)) {
|
||||
fs.unlinkSync(this.dbPath);
|
||||
}
|
||||
Config.Server.database.type = DatabaseType.sqlite;
|
||||
Config.Server.database.sqlite.storage = this.dbPath;
|
||||
Config.Server.Database.type = DatabaseType.sqlite;
|
||||
Config.Server.Database.sqlite.storage = this.dbPath;
|
||||
await ObjectManagers.InitSQLManagers();
|
||||
};
|
||||
|
||||
|
@ -1 +1 @@
|
||||
export const DataStructureVersion = 13;
|
||||
export const DataStructureVersion = 14;
|
||||
|
@ -6,7 +6,7 @@ export enum DatabaseType {
|
||||
}
|
||||
|
||||
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 {
|
||||
@ -55,8 +55,8 @@ export interface IndexingConfig {
|
||||
folderPreviewSize: number;
|
||||
cachedFolderTimeout: number; // Do not rescans the folder if seems ok
|
||||
reIndexingSensitivity: ReIndexingSensitivity;
|
||||
excludeFolderList: string[]
|
||||
excludeFileList: string[]
|
||||
excludeFolderList: string[];
|
||||
excludeFileList: string[];
|
||||
}
|
||||
|
||||
export interface ThreadingConfig {
|
||||
@ -77,20 +77,31 @@ export interface TaskConfig {
|
||||
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 {
|
||||
port: number;
|
||||
host: string;
|
||||
imagesFolder: string;
|
||||
thumbnail: ThumbnailConfig;
|
||||
threading: ThreadingConfig;
|
||||
database: DataBaseConfig;
|
||||
sharing: SharingConfig;
|
||||
Thumbnail: ThumbnailConfig;
|
||||
Threading: ThreadingConfig;
|
||||
Database: DataBaseConfig;
|
||||
Sharing: SharingConfig;
|
||||
sessionTimeout: number;
|
||||
indexing: IndexingConfig;
|
||||
Indexing: IndexingConfig;
|
||||
photoMetadataSize: number;
|
||||
duplicates: DuplicatesConfig;
|
||||
log: LogConfig;
|
||||
tasks: TaskConfig;
|
||||
Duplicates: DuplicatesConfig;
|
||||
Log: LogConfig;
|
||||
Tasks: TaskConfig;
|
||||
Video: VideoConfig;
|
||||
}
|
||||
|
||||
export interface IPrivateConfig {
|
||||
|
@ -12,8 +12,6 @@ import * as path from 'path';
|
||||
import {ConfigLoader} from 'typeconfig';
|
||||
import {Utils} from '../../Utils';
|
||||
import {UserRoles} from '../../entities/UserDTO';
|
||||
import {TaskScheduleDTO} from '../../entities/task/TaskScheduleDTO';
|
||||
import {Config} from './Config';
|
||||
|
||||
/**
|
||||
* This configuration will be only at backend
|
||||
@ -24,19 +22,19 @@ export class PrivateConfigClass extends PublicConfigClass implements IPrivateCon
|
||||
port: 80,
|
||||
host: '0.0.0.0',
|
||||
imagesFolder: 'demo/images',
|
||||
thumbnail: {
|
||||
Thumbnail: {
|
||||
folder: 'demo/TEMP',
|
||||
processingLibrary: ThumbnailProcessingLib.sharp,
|
||||
qualityPriority: true,
|
||||
personFaceMargin: 0.6
|
||||
},
|
||||
log: {
|
||||
Log: {
|
||||
level: LogLevel.info,
|
||||
sqlLevel: SQLLogLevel.error
|
||||
},
|
||||
sessionTimeout: 1000 * 60 * 60 * 24 * 7,
|
||||
photoMetadataSize: 512 * 1024,
|
||||
database: {
|
||||
Database: {
|
||||
type: DatabaseType.sqlite,
|
||||
mysql: {
|
||||
host: '',
|
||||
@ -49,31 +47,40 @@ export class PrivateConfigClass extends PublicConfigClass implements IPrivateCon
|
||||
storage: 'sqlite.db'
|
||||
}
|
||||
},
|
||||
sharing: {
|
||||
Sharing: {
|
||||
updateTimeout: 1000 * 60 * 5
|
||||
},
|
||||
threading: {
|
||||
Threading: {
|
||||
enable: true,
|
||||
thumbnailThreads: 0
|
||||
},
|
||||
indexing: {
|
||||
Indexing: {
|
||||
folderPreviewSize: 2,
|
||||
cachedFolderTimeout: 1000 * 60 * 60,
|
||||
reIndexingSensitivity: ReIndexingSensitivity.low,
|
||||
excludeFolderList: [],
|
||||
excludeFileList: []
|
||||
},
|
||||
duplicates: {
|
||||
Duplicates: {
|
||||
listingLimit: 1000
|
||||
},
|
||||
tasks: {
|
||||
Tasks: {
|
||||
scheduled: []
|
||||
},
|
||||
Video: {
|
||||
transcoding: {
|
||||
bitRate: 5 * 1024 * 1024,
|
||||
codec: 'libx264',
|
||||
format: 'mp4',
|
||||
fps: 25,
|
||||
resolution: 720
|
||||
}
|
||||
}
|
||||
};
|
||||
private ConfigLoader: any;
|
||||
|
||||
public setDatabaseType(type: DatabaseType) {
|
||||
this.Server.database.type = type;
|
||||
this.Server.Database.type = type;
|
||||
if (type === DatabaseType.memory) {
|
||||
this.Client.Search.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) {
|
||||
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) {
|
||||
throw new Error('Unknown Server.log.level, found: ' + this.Server.log.level);
|
||||
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);
|
||||
}
|
||||
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);
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -15,4 +15,5 @@ export interface VideoMetadata extends MediaMetadata {
|
||||
bitRate: number;
|
||||
duration: number; // in milliseconds
|
||||
fileSize: number;
|
||||
fps: number;
|
||||
}
|
||||
|
@ -2,7 +2,7 @@ export type fieldType = 'string' | 'number' | 'boolean';
|
||||
|
||||
|
||||
export enum DefaultsTasks {
|
||||
Indexing = 1, 'Database Reset' = 2
|
||||
Indexing = 1, 'Database Reset' = 2, 'Video Converting' = 3
|
||||
}
|
||||
|
||||
export interface ConfigTemplateEntry {
|
||||
|
@ -33,12 +33,19 @@ export class MediaIcon {
|
||||
this.media.directory.path, this.media.directory.name, this.media.name, 'icon');
|
||||
}
|
||||
|
||||
getPhotoPath() {
|
||||
getMediaPath() {
|
||||
return Utils.concatUrls(Config.Client.urlBase,
|
||||
'/api/gallery/content/',
|
||||
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 {
|
||||
// is gridphoto
|
||||
|
@ -92,6 +92,12 @@
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.controls-zoom {
|
||||
bottom: 0;
|
||||
z-index: 3;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.controls-playback {
|
||||
padding-right: 15px;
|
||||
bottom: 0;
|
||||
@ -103,6 +109,7 @@
|
||||
padding-right: 15px;
|
||||
bottom: 0;
|
||||
position: absolute;
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
.controls-video .oi,
|
||||
|
@ -5,7 +5,7 @@
|
||||
<div class="controls controls-top">
|
||||
<a class="highlight control-button"
|
||||
*ngIf="activePhoto"
|
||||
[href]="activePhoto.gridMedia.getPhotoPath()"
|
||||
[href]="activePhoto.gridMedia.getMediaPath()"
|
||||
[download]="activePhoto.gridMedia.media.name">
|
||||
<span class="oi oi-data-transfer-download"
|
||||
title="download" i18n-title></span>
|
||||
@ -55,7 +55,7 @@
|
||||
[style.left.px]="photoFrameDim.width/2"
|
||||
[style.width.px]="faceContainerDim.width"
|
||||
[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
|
||||
class="face"
|
||||
[routerLink]="['/search', face.name, {type: SearchTypes[SearchTypes.person]}]"
|
||||
|
@ -50,6 +50,9 @@
|
||||
<div class="col-6" *ngIf="VideoData.duration">
|
||||
<ng-container i18n>duration</ng-container>
|
||||
: {{VideoData.duration | duration}}</div>
|
||||
<div class="col-6" *ngIf="VideoData.fps">
|
||||
fps: {{VideoData.fps}}/s
|
||||
</div>
|
||||
<div class="col-6" *ngIf="VideoData.bitRate">
|
||||
<ng-container i18n>bit rate</ng-container>
|
||||
: {{VideoData.bitRate | fileSize}}/s
|
||||
|
@ -22,7 +22,7 @@
|
||||
(error)="onImageError()"
|
||||
(timeupdate)="onVideoProgress()"
|
||||
#video>
|
||||
<source [src]="gridMedia.getPhotoPath()" type="{{getVideoType()}}">
|
||||
<source [src]="gridMedia.getBestFitMediaPath()" type="{{getVideoType()}}">
|
||||
</video>
|
||||
|
||||
</div>
|
||||
|
@ -55,7 +55,7 @@ export class GalleryLightboxMediaComponent implements OnChanges {
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
@ -144,7 +144,7 @@ export class GalleryLightboxMediaComponent implements OnChanges {
|
||||
onImageError() {
|
||||
// TODO:handle error
|
||||
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 {
|
||||
return this.gridMedia.getPhotoPath();
|
||||
return this.gridMedia.getMediaPath();
|
||||
}
|
||||
|
||||
public showThumbnail(): boolean {
|
||||
|
@ -25,7 +25,7 @@ export class DatabaseSettingsComponent extends SettingsComponent<DataBaseConfig>
|
||||
_settingsService: DatabaseSettingsService,
|
||||
notification: NotificationService,
|
||||
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() {
|
||||
|
@ -13,7 +13,7 @@ export class FacesSettingsService extends AbstractSettingsService<ClientConfig.F
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -37,7 +37,7 @@ export class IndexingSettingsComponent extends SettingsComponent<IndexingConfig,
|
||||
<any>_settingsService,
|
||||
notification,
|
||||
i18n,
|
||||
s => s.Server.indexing);
|
||||
s => s.Server.Indexing);
|
||||
|
||||
}
|
||||
|
||||
|
@ -41,7 +41,7 @@ export class IndexingSettingsService extends AbstractSettingsService<IndexingCon
|
||||
|
||||
|
||||
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,
|
||||
i18n: I18n) {
|
||||
super(i18n('Other'), _authService, _navigation, _settingsService, notification, i18n, s => ({
|
||||
Server: s.Server.threading,
|
||||
Server: s.Server.Threading,
|
||||
Client: s.Client.Other
|
||||
}));
|
||||
this.types = Utils.enumToArray(SortingMethods);
|
||||
|
@ -19,7 +19,7 @@ export class RandomPhotoSettingsService extends AbstractSettingsService<ClientCo
|
||||
}
|
||||
|
||||
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> {
|
||||
|
@ -13,7 +13,7 @@ export class SearchSettingsService extends AbstractSettingsService<ClientConfig.
|
||||
}
|
||||
|
||||
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> {
|
||||
|
@ -82,31 +82,31 @@ export class SettingsService {
|
||||
languages: []
|
||||
},
|
||||
Server: {
|
||||
database: {
|
||||
Database: {
|
||||
type: DatabaseType.memory
|
||||
},
|
||||
log: {
|
||||
Log: {
|
||||
level: LogLevel.info,
|
||||
sqlLevel: SQLLogLevel.error
|
||||
},
|
||||
sharing: {
|
||||
Sharing: {
|
||||
updateTimeout: 2000
|
||||
},
|
||||
imagesFolder: '',
|
||||
port: 80,
|
||||
host: '0.0.0.0',
|
||||
thumbnail: {
|
||||
Thumbnail: {
|
||||
personFaceMargin: 0.1,
|
||||
folder: '',
|
||||
qualityPriority: true,
|
||||
processingLibrary: ThumbnailProcessingLib.sharp
|
||||
},
|
||||
threading: {
|
||||
Threading: {
|
||||
enable: true,
|
||||
thumbnailThreads: 0
|
||||
},
|
||||
sessionTimeout: 0,
|
||||
indexing: {
|
||||
Indexing: {
|
||||
cachedFolderTimeout: 0,
|
||||
folderPreviewSize: 0,
|
||||
reIndexingSensitivity: ReIndexingSensitivity.medium,
|
||||
@ -114,11 +114,20 @@ export class SettingsService {
|
||||
excludeFileList: []
|
||||
},
|
||||
photoMetadataSize: 512 * 1024,
|
||||
duplicates: {
|
||||
Duplicates: {
|
||||
listingLimit: 1000
|
||||
},
|
||||
tasks: {
|
||||
Tasks: {
|
||||
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 {
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -46,7 +46,7 @@ export class TasksSettingsComponent extends SettingsComponent<TaskConfig, TasksS
|
||||
<any>_settingsService,
|
||||
notification,
|
||||
i18n,
|
||||
s => s.Server.tasks);
|
||||
s => s.Server.Tasks);
|
||||
|
||||
this.hasAvailableSettings = !this.simplifiedMode;
|
||||
this.taskTriggerType = Utils.enumToArray(TaskTriggerType);
|
||||
|
@ -29,7 +29,7 @@ export class ThumbnailSettingsComponent
|
||||
i18n: I18n) {
|
||||
super(i18n('Thumbnail'), _authService, _navigation, _settingsService, notification, i18n, s => ({
|
||||
client: s.Client.Thumbnail,
|
||||
server: s.Server.thumbnail
|
||||
server: s.Server.Thumbnail
|
||||
}));
|
||||
}
|
||||
|
||||
|
@ -62,13 +62,13 @@ export class SQLTestHelper {
|
||||
private async initSQLite() {
|
||||
await this.resetSQLite();
|
||||
|
||||
Config.Server.database.type = DatabaseType.sqlite;
|
||||
Config.Server.database.sqlite.storage = this.dbPath;
|
||||
Config.Server.Database.type = DatabaseType.sqlite;
|
||||
Config.Server.Database.sqlite.storage = this.dbPath;
|
||||
}
|
||||
|
||||
private async initMySQL() {
|
||||
Config.Server.database.type = DatabaseType.mysql;
|
||||
Config.Server.database.mysql.database = 'pigallery2_test';
|
||||
Config.Server.Database.type = DatabaseType.mysql;
|
||||
Config.Server.Database.mysql.database = 'pigallery2_test';
|
||||
|
||||
await this.resetMySQL();
|
||||
}
|
||||
@ -85,8 +85,8 @@ export class SQLTestHelper {
|
||||
}
|
||||
|
||||
private async resetMySQL() {
|
||||
Config.Server.database.type = DatabaseType.mysql;
|
||||
Config.Server.database.mysql.database = 'pigallery2_test';
|
||||
Config.Server.Database.type = DatabaseType.mysql;
|
||||
Config.Server.Database.mysql.database = 'pigallery2_test';
|
||||
const conn = await SQLConnection.getConnection();
|
||||
await conn.query('DROP DATABASE IF 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() {
|
||||
Config.Server.database.type = DatabaseType.mysql;
|
||||
Config.Server.database.mysql.database = 'pigallery2_test';
|
||||
Config.Server.Database.type = DatabaseType.mysql;
|
||||
Config.Server.Database.mysql.database = 'pigallery2_test';
|
||||
const conn = await SQLConnection.getConnection();
|
||||
await conn.query('DROP DATABASE IF EXISTS ' + conn.options.database);
|
||||
await SQLConnection.close();
|
||||
|
@ -31,8 +31,8 @@ describe('Typeorm integration', () => {
|
||||
fs.mkdirSync(tempDir);
|
||||
}
|
||||
|
||||
Config.Server.database.type = DatabaseType.sqlite;
|
||||
Config.Server.database.sqlite.storage = dbPath;
|
||||
Config.Server.Database.type = DatabaseType.sqlite;
|
||||
Config.Server.Database.sqlite.storage = dbPath;
|
||||
|
||||
};
|
||||
|
||||
|
@ -457,7 +457,7 @@ describe('IndexingManager', (sqlHelper: SQLTestHelper) => {
|
||||
});
|
||||
|
||||
it('with re indexing severity low', async () => {
|
||||
Config.Server.indexing.reIndexingSensitivity = ReIndexingSensitivity.low;
|
||||
Config.Server.Indexing.reIndexingSensitivity = ReIndexingSensitivity.low;
|
||||
|
||||
// @ts-ignore
|
||||
fs.statSync = () => ({ctime: new Date(dirTime), mtime: new Date(dirTime)});
|
||||
|
Loading…
x
Reference in New Issue
Block a user