improve settings

This commit is contained in:
Patrik J. Braun 2019-12-15 14:40:31 +01:00
parent 95b06ffc63
commit 5160e19a35
36 changed files with 428 additions and 212 deletions

View File

@ -359,7 +359,7 @@ export class SettingsMWs {
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.enabled = settings.Server.enabled;
original.Server.Threading.thumbnailThreads = settings.Server.thumbnailThreads; original.Server.Threading.thumbnailThreads = settings.Server.thumbnailThreads;
original.save(); original.save();
await ConfigDiagnostics.runDiagnostics(); await ConfigDiagnostics.runDiagnostics();

View File

@ -11,7 +11,7 @@ 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.enabled === true) {
DiskManager.threadPool = new DiskManagerTH(1); DiskManager.threadPool = new DiskManagerTH(1);
} }
} }
@ -23,7 +23,7 @@ export class DiskManager {
let directory: DirectoryDTO; let directory: DirectoryDTO;
if (Config.Server.Threading.enable === true) { if (Config.Server.Threading.enabled === true) {
directory = await DiskManager.threadPool.execute(relativeDirectoryName, settings); directory = await DiskManager.threadPool.execute(relativeDirectoryName, settings);
} else { } else {
directory = await DiskMangerWorker.scanDirectory(relativeDirectoryName, settings); directory = await DiskMangerWorker.scanDirectory(relativeDirectoryName, settings);

View File

@ -81,13 +81,13 @@ export class ConfigDiagnostics {
} }
} }
static async testThumbnailLib(processingLibrary: ServerConfig.ThumbnailProcessingLib) { static async testThumbnailLib(processingLibrary: ServerConfig.PhotoProcessingLib) {
switch (processingLibrary) { switch (processingLibrary) {
case ServerConfig.ThumbnailProcessingLib.sharp: case ServerConfig.PhotoProcessingLib.sharp:
const sharp = require('sharp'); const sharp = require('sharp');
sharp(); sharp();
break; break;
case ServerConfig.ThumbnailProcessingLib.gm: case ServerConfig.PhotoProcessingLib.gm:
const gm = require('gm'); const gm = require('gm');
await new Promise((resolve, reject) => { await new Promise((resolve, reject) => {
gm(ProjectPath.FrontendFolder + '/assets/icon.png').size((err: Error) => { gm(ProjectPath.FrontendFolder + '/assets/icon.png').size((err: Error) => {
@ -120,8 +120,10 @@ export class ConfigDiagnostics {
} }
static async testServerThumbnailConfig(thumbnailConfig: ServerConfig.ThumbnailConfig) { public static async testServerThumbnailConfig(server: ServerConfig.ThumbnailConfig) {
await ConfigDiagnostics.testThumbnailLib(thumbnailConfig.processingLibrary); if (server.personFaceMargin < 0 || server.personFaceMargin > 1) {
throw new Error('personFaceMargin should be between 0 and 1');
}
} }
static async testClientThumbnailConfig(thumbnailConfig: ClientConfig.ThumbnailConfig) { static async testClientThumbnailConfig(thumbnailConfig: ClientConfig.ThumbnailConfig) {
@ -218,19 +220,19 @@ export class ConfigDiagnostics {
} }
} }
if (Config.Server.Media.Thumbnail.processingLibrary !== ServerConfig.ThumbnailProcessingLib.Jimp) { if (Config.Server.Media.photoProcessingLibrary !== ServerConfig.PhotoProcessingLib.Jimp) {
try { try {
await ConfigDiagnostics.testThumbnailLib(Config.Server.Media.Thumbnail.processingLibrary); await ConfigDiagnostics.testThumbnailLib(Config.Server.Media.photoProcessingLibrary);
} 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.' +
' \'' + ServerConfig.ThumbnailProcessingLib[Config.Server.Media.Thumbnail.processingLibrary] + '\' node module is not found.' + ' \'' + ServerConfig.PhotoProcessingLib[Config.Server.Media.photoProcessingLibrary] + '\' 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.' +
' \'' + ServerConfig.ThumbnailProcessingLib[Config.Server.Media.Thumbnail.processingLibrary] + '\' node module is not found.' + ' \'' + ServerConfig.PhotoProcessingLib[Config.Server.Media.photoProcessingLibrary] + '\' node module is not found.' +
' Falling back temporally to JS based thumbnail generation'); ' Falling back temporally to JS based thumbnail generation');
Config.Server.Media.Thumbnail.processingLibrary = ServerConfig.ThumbnailProcessingLib.Jimp; Config.Server.Media.photoProcessingLibrary = ServerConfig.PhotoProcessingLib.Jimp;
} }
} }
@ -343,4 +345,5 @@ export class ConfigDiagnostics {
} }
} }
} }

View File

@ -22,7 +22,7 @@ export class PhotoProcessing {
} }
if (Config.Server.Threading.enable === true) { if (Config.Server.Threading.enabled === true) {
if (Config.Server.Threading.thumbnailThreads > 0) { if (Config.Server.Threading.thumbnailThreads > 0) {
Config.Client.Media.Thumbnail.concurrentThumbnailGenerations = Config.Server.Threading.thumbnailThreads; Config.Client.Media.Thumbnail.concurrentThumbnailGenerations = Config.Server.Threading.thumbnailThreads;
} else { } else {
@ -32,12 +32,12 @@ export class PhotoProcessing {
Config.Client.Media.Thumbnail.concurrentThumbnailGenerations = 1; Config.Client.Media.Thumbnail.concurrentThumbnailGenerations = 1;
} }
if (Config.Server.Threading.enable === true && if (Config.Server.Threading.enabled === true &&
Config.Server.Media.Thumbnail.processingLibrary === ServerConfig.ThumbnailProcessingLib.Jimp) { Config.Server.Media.photoProcessingLibrary === ServerConfig.PhotoProcessingLib.Jimp) {
this.taskQue = new ThumbnailTH(Config.Client.Media.Thumbnail.concurrentThumbnailGenerations); this.taskQue = new ThumbnailTH(Config.Client.Media.Thumbnail.concurrentThumbnailGenerations);
} else { } else {
this.taskQue = new TaskExecuter(Config.Client.Media.Thumbnail.concurrentThumbnailGenerations, this.taskQue = new TaskExecuter(Config.Client.Media.Thumbnail.concurrentThumbnailGenerations,
(input => ThumbnailWorker.render(input, Config.Server.Media.Thumbnail.processingLibrary))); (input => ThumbnailWorker.render(input, Config.Server.Media.photoProcessingLibrary)));
} }
this.initDone = true; this.initDone = true;

View File

@ -2,6 +2,7 @@ import {ITask} from './tasks/ITask';
import {IndexingTask} from './tasks/IndexingTask'; import {IndexingTask} from './tasks/IndexingTask';
import {DBRestTask} from './tasks/DBResetTask'; import {DBRestTask} from './tasks/DBResetTask';
import {VideoConvertingTask} from './tasks/VideoConvertingTask'; import {VideoConvertingTask} from './tasks/VideoConvertingTask';
import {PhotoConvertingTask} from './tasks/PhotoConvertingTask';
export class TaskRepository { export class TaskRepository {
@ -28,3 +29,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()); TaskRepository.Instance.register(new VideoConvertingTask());
TaskRepository.Instance.register(new PhotoConvertingTask());

View File

@ -2,17 +2,15 @@ import {TaskProgressDTO, TaskState} from '../../../../common/entities/settings/T
import {ConfigTemplateEntry} from '../../../../common/entities/task/TaskDTO'; import {ConfigTemplateEntry} from '../../../../common/entities/task/TaskDTO';
import {Task} from './Task'; import {Task} from './Task';
import * as path from 'path'; import * as path from 'path';
import * as fs from 'fs';
import * as util from 'util';
import {DiskManager} from '../../DiskManger'; import {DiskManager} from '../../DiskManger';
import {DiskMangerWorker} from '../../threading/DiskMangerWorker'; import {DiskMangerWorker} from '../../threading/DiskMangerWorker';
import {DirectoryDTO} from '../../../../common/entities/DirectoryDTO'; import {DirectoryDTO} from '../../../../common/entities/DirectoryDTO';
import {Logger} from '../../../Logger';
declare var global: NodeJS.Global; declare var global: NodeJS.Global;
const LOG_TAG = '[FileTask]'; const LOG_TAG = '[FileTask]';
const existsPr = util.promisify(fs.exists);
export abstract class FileTask<T> extends Task { export abstract class FileTask<T> extends Task {
@ -58,7 +56,12 @@ export abstract class FileTask<T> extends Task {
this.progress.left = this.fileQueue.length; this.progress.left = this.fileQueue.length;
this.progress.progress++; this.progress.progress++;
this.progress.comment = 'processing: ' + file; this.progress.comment = 'processing: ' + file;
await this.processFile(file); try {
await this.processFile(file);
} catch (e) {
console.error(e);
Logger.error(LOG_TAG, 'Error during processing file: ' + e.toString());
}
} }
return this.progress; return this.progress;
} }

View File

@ -1,30 +1,18 @@
import {TaskProgressDTO, TaskState} from '../../../../common/entities/settings/TaskProgressDTO'; import {TaskProgressDTO, TaskState} 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 {Logger} from '../../../Logger';
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 {ProjectPath} from '../../../ProjectPath';
import {ThumbnailGeneratorMWs} from '../../../middlewares/thumbnail/ThumbnailGeneratorMWs';
import {Task} from './Task'; import {Task} from './Task';
import {ConfigTemplateEntry, DefaultsTasks} from '../../../../common/entities/task/TaskDTO'; import {ConfigTemplateEntry, DefaultsTasks} from '../../../../common/entities/task/TaskDTO';
import {ServerConfig} from '../../../../common/config/private/IPrivateConfig'; import {ServerConfig} from '../../../../common/config/private/IPrivateConfig';
import {PhotoProcessing} from '../../fileprocessing/PhotoProcessing';
declare var global: NodeJS.Global; declare var global: NodeJS.Global;
const LOG_TAG = '[IndexingTask]'; const LOG_TAG = '[IndexingTask]';
export class IndexingTask extends Task<{ createThumbnails: boolean }> { export class IndexingTask extends Task {
public readonly Name = DefaultsTasks[DefaultsTasks.Indexing]; public readonly Name = DefaultsTasks[DefaultsTasks.Indexing];
directoriesToIndex: string[] = []; directoriesToIndex: string[] = [];
public readonly ConfigTemplate: ConfigTemplateEntry[] = [{ public readonly ConfigTemplate: ConfigTemplateEntry[] = null;
id: 'createThumbnails',
type: 'boolean',
name: 'With thumbnails',
defaultValue: false
}];
public get Supported(): boolean { public get Supported(): boolean {
return Config.Server.Database.type !== ServerConfig.DatabaseType.memory; return Config.Server.Database.type !== ServerConfig.DatabaseType.memory;
@ -54,31 +42,6 @@ export class IndexingTask extends Task<{ createThumbnails: boolean }> {
for (let i = 0; i < scanned.directories.length; i++) { for (let i = 0; i < scanned.directories.length; i++) {
this.directoriesToIndex.push(path.join(scanned.directories[i].path, scanned.directories[i].name)); this.directoriesToIndex.push(path.join(scanned.directories[i].path, scanned.directories[i].name));
} }
if (this.config.createThumbnails) {
for (let i = 0; i < scanned.media.length; i++) {
try {
const media = scanned.media[i];
const mPath = path.join(ProjectPath.ImageFolder, media.directory.path, media.directory.name, media.name);
const thPath = path.join(ProjectPath.ThumbnailFolder,
PhotoProcessing.generateThumbnailName(mPath, Config.Client.Media.Thumbnail.thumbnailSizes[0]));
if (fs.existsSync(thPath)) { // skip existing thumbnails
continue;
}
await ThumbnailWorker.render(<RendererInput>{
type: MediaDTO.isVideo(media) ? ThumbnailSourceType.Video : ThumbnailSourceType.Photo,
mediaPath: mPath,
size: Config.Client.Media.Thumbnail.thumbnailSizes[0],
outPath: thPath,
makeSquare: false,
qualityPriority: Config.Server.Media.Thumbnail.qualityPriority
}, Config.Server.Media.Thumbnail.processingLibrary);
} catch (e) {
console.error(e);
Logger.error(LOG_TAG, 'Error during indexing job: ' + e.toString());
}
}
}
return this.progress; return this.progress;
} }

View File

@ -14,14 +14,14 @@ const existsPr = util.promisify(fs.exists);
export class PhotoConvertingTask extends FileTask<string> { export class PhotoConvertingTask extends FileTask<string> {
public readonly Name = DefaultsTasks[DefaultsTasks['Video Converting']]; public readonly Name = DefaultsTasks[DefaultsTasks['Photo Converting']];
constructor() { constructor() {
super({noVideo: true, noMetaFile: true}); super({noVideo: true, noMetaFile: true});
} }
public get Supported(): boolean { public get Supported(): boolean {
return Config.Server.Media.Photo.Converting.enabled === true; return Config.Client.Media.Photo.Converting.enabled === true;
} }
protected async processDirectory(directory: DirectoryDTO): Promise<string[]> { protected async processDirectory(directory: DirectoryDTO): Promise<string[]> {
@ -40,7 +40,7 @@ export class PhotoConvertingTask extends FileTask<string> {
} }
protected async processFile(file: string): Promise<void> { protected async processFile(file: string): Promise<void> {
await PhotoProcessing.generateThumbnail(file, Config.Server.Media.Photo.Converting.resolution, ThumbnailSourceType.Photo, false); await PhotoProcessing.convertPhoto(file, Config.Server.Media.Photo.Converting.resolution);
} }

View File

@ -103,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.Media.Thumbnail.processingLibrary renderer: Config.Server.Media.photoProcessingLibrary
}); });
} }
} }

View File

@ -9,16 +9,16 @@ export class ThumbnailWorker {
private static imageRenderer: (input: RendererInput) => Promise<void> = null; private static imageRenderer: (input: RendererInput) => Promise<void> = null;
private static videoRenderer: (input: RendererInput) => Promise<void> = null; private static videoRenderer: (input: RendererInput) => Promise<void> = null;
private static rendererType: ServerConfig.ThumbnailProcessingLib = null; private static rendererType: ServerConfig.PhotoProcessingLib = null;
public static render(input: RendererInput, renderer: ServerConfig.ThumbnailProcessingLib): Promise<void> { public static render(input: RendererInput, renderer: ServerConfig.PhotoProcessingLib): Promise<void> {
if (input.type === ThumbnailSourceType.Photo) { if (input.type === ThumbnailSourceType.Photo) {
return this.renderFromImage(input, renderer); return this.renderFromImage(input, renderer);
} }
return this.renderFromVideo(input); return this.renderFromVideo(input);
} }
public static renderFromImage(input: RendererInput, renderer: ServerConfig.ThumbnailProcessingLib): Promise<void> { public static renderFromImage(input: RendererInput, renderer: ServerConfig.PhotoProcessingLib): Promise<void> {
if (ThumbnailWorker.rendererType !== renderer) { if (ThumbnailWorker.rendererType !== renderer) {
ThumbnailWorker.imageRenderer = ImageRendererFactory.build(renderer); ThumbnailWorker.imageRenderer = ImageRendererFactory.build(renderer);
ThumbnailWorker.rendererType = renderer; ThumbnailWorker.rendererType = renderer;
@ -117,13 +117,13 @@ export class VideoRendererFactory {
export class ImageRendererFactory { export class ImageRendererFactory {
public static build(renderer: ServerConfig.ThumbnailProcessingLib): (input: RendererInput) => Promise<void> { public static build(renderer: ServerConfig.PhotoProcessingLib): (input: RendererInput) => Promise<void> {
switch (renderer) { switch (renderer) {
case ServerConfig.ThumbnailProcessingLib.Jimp: case ServerConfig.PhotoProcessingLib.Jimp:
return ImageRendererFactory.Jimp(); return ImageRendererFactory.Jimp();
case ServerConfig.ThumbnailProcessingLib.gm: case ServerConfig.PhotoProcessingLib.gm:
return ImageRendererFactory.Gm(); return ImageRendererFactory.Gm();
case ServerConfig.ThumbnailProcessingLib.sharp: case ServerConfig.PhotoProcessingLib.sharp:
return ImageRendererFactory.Sharp(); return ImageRendererFactory.Sharp();
} }
throw new Error('unknown renderer'); throw new Error('unknown renderer');

View File

@ -54,7 +54,7 @@ export interface DiskManagerTask extends WorkerTask {
export interface ThumbnailTask extends WorkerTask { export interface ThumbnailTask extends WorkerTask {
input: RendererInput; input: RendererInput;
renderer: ServerConfig.ThumbnailProcessingLib; renderer: ServerConfig.PhotoProcessingLib;
} }
export module WorkerTask { export module WorkerTask {

View File

@ -14,7 +14,7 @@ export module ServerConfig {
none = 1, error = 2, all = 3 none = 1, error = 2, all = 3
} }
export enum ThumbnailProcessingLib { export enum PhotoProcessingLib {
sharp = 3, sharp = 3,
Jimp = 1, Jimp = 1,
gm = 2, gm = 2,
@ -43,7 +43,6 @@ export module ServerConfig {
} }
export interface ThumbnailConfig { export interface ThumbnailConfig {
processingLibrary: ThumbnailProcessingLib;
qualityPriority: boolean; qualityPriority: boolean;
personFaceMargin: number; // in ration [0-1] personFaceMargin: number; // in ration [0-1]
} }
@ -65,7 +64,7 @@ export module ServerConfig {
} }
export interface ThreadingConfig { export interface ThreadingConfig {
enable: boolean; enabled: boolean;
thumbnailThreads: number; thumbnailThreads: number;
} }
@ -107,6 +106,7 @@ export module ServerConfig {
export interface MediaConfig { export interface MediaConfig {
folder: string; folder: string;
tempFolder: string; tempFolder: string;
photoProcessingLibrary: PhotoProcessingLib;
Video: VideoConfig; Video: VideoConfig;
Photo: PhotoConfig; Photo: PhotoConfig;
Thumbnail: ThumbnailConfig; Thumbnail: ThumbnailConfig;

View File

@ -14,8 +14,8 @@ export class PrivateConfigDefaultsClass extends PublicConfigClass implements IPr
Media: { Media: {
folder: 'demo/images', folder: 'demo/images',
tempFolder: 'demo/tmp', tempFolder: 'demo/tmp',
photoProcessingLibrary: ServerConfig.PhotoProcessingLib.sharp,
Thumbnail: { Thumbnail: {
processingLibrary: ServerConfig.ThumbnailProcessingLib.sharp,
qualityPriority: true, qualityPriority: true,
personFaceMargin: 0.6 personFaceMargin: 0.6
}, },
@ -61,7 +61,7 @@ export class PrivateConfigDefaultsClass extends PublicConfigClass implements IPr
updateTimeout: 1000 * 60 * 5 updateTimeout: 1000 * 60 * 5
}, },
Threading: { Threading: {
enable: true, enabled: true,
thumbnailThreads: 0 thumbnailThreads: 0
}, },
Indexing: { Indexing: {

View File

@ -2,7 +2,7 @@ export type fieldType = 'string' | 'number' | 'boolean';
export enum DefaultsTasks { export enum DefaultsTasks {
Indexing = 1, 'Database Reset' = 2, 'Video Converting' = 3 Indexing = 1, 'Database Reset' = 2, 'Video Converting' = 3, 'Photo Converting' = 4, 'Thumbnail Generation' = 5
} }
export interface ConfigTemplateEntry { export interface ConfigTemplateEntry {

View File

@ -87,6 +87,7 @@ import {TimepickerModule} from 'ngx-bootstrap/timepicker';
import {TimeStampDatePickerComponent} from './ui/utils/timestamp-datepicker/datepicker.component'; import {TimeStampDatePickerComponent} from './ui/utils/timestamp-datepicker/datepicker.component';
import {TimeStampTimePickerComponent} from './ui/utils/timestamp-timepicker/timepicker.component'; import {TimeStampTimePickerComponent} from './ui/utils/timestamp-timepicker/timepicker.component';
import {TasksProgressComponent} from './ui/settings/tasks/progress/progress.tasks.settings.component'; import {TasksProgressComponent} from './ui/settings/tasks/progress/progress.tasks.settings.component';
import {PhotoSettingsComponent} from './ui/settings/photo/photo.settings.component';
@ -188,6 +189,7 @@ export function translationsFactory(locale: string) {
MapSettingsComponent, MapSettingsComponent,
ThumbnailSettingsComponent, ThumbnailSettingsComponent,
VideoSettingsComponent, VideoSettingsComponent,
PhotoSettingsComponent,
MetaFileSettingsComponent, MetaFileSettingsComponent,
SearchSettingsComponent, SearchSettingsComponent,
ShareSettingsComponent, ShareSettingsComponent,

View File

@ -53,8 +53,10 @@
<button class="btn btn-link nav-link text-md-left py-md-1 px-md-0" <button class="btn btn-link nav-link text-md-left py-md-1 px-md-0"
*ngFor="let s of contents; let i=index;" *ngFor="let s of contents; let i=index;"
(click)="scrollTo(i)" (click)="scrollTo(i)"
[hidden]="!s.hasAvailableSettings" [hidden]="!s.HasAvailableSettings">
>{{s.Name}}</button> {{s.Name}}<!--
--><ng-container *ngIf="s.Changed">*</ng-container>
</button>
</div> </div>
</div> </div>
@ -63,44 +65,47 @@
<div class="col-md-10"> <div class="col-md-10">
<app-settings-basic #setting #basic <app-settings-basic #setting #basic
[simplifiedMode]="simplifiedMode" [simplifiedMode]="simplifiedMode"
[hidden]="!basic.hasAvailableSettings"></app-settings-basic> [hidden]="!basic.HasAvailableSettings"></app-settings-basic>
<app-settings-usermanager #setting #userManager <app-settings-usermanager #setting #userManager
[hidden]="!userManager.hasAvailableSettings"></app-settings-usermanager> [hidden]="!userManager.HasAvailableSettings"></app-settings-usermanager>
<app-settings-database #setting #database <app-settings-database #setting #database
[simplifiedMode]="simplifiedMode" [simplifiedMode]="simplifiedMode"
[hidden]="!database.hasAvailableSettings"></app-settings-database> [hidden]="!database.HasAvailableSettings"></app-settings-database>
<app-settings-thumbnail #setting #thumbnail <app-settings-photo #setting #photo
[hidden]="!thumbnail.hasAvailableSettings" [hidden]="!photo.HasAvailableSettings"
[simplifiedMode]="simplifiedMode"></app-settings-thumbnail> [simplifiedMode]="simplifiedMode"></app-settings-photo>
<app-settings-video #setting #video <app-settings-video #setting #video
[hidden]="!video.hasAvailableSettings" [hidden]="!video.HasAvailableSettings"
[simplifiedMode]="simplifiedMode"></app-settings-video> [simplifiedMode]="simplifiedMode"></app-settings-video>
<app-settings-thumbnail #setting #thumbnail
[hidden]="!thumbnail.HasAvailableSettings"
[simplifiedMode]="simplifiedMode"></app-settings-thumbnail>
<app-settings-search #setting #search <app-settings-search #setting #search
[hidden]="!search.hasAvailableSettings" [hidden]="!search.HasAvailableSettings"
[simplifiedMode]="simplifiedMode"></app-settings-search> [simplifiedMode]="simplifiedMode"></app-settings-search>
<app-settings-share #setting #share <app-settings-share #setting #share
[hidden]="!share.hasAvailableSettings" [hidden]="!share.HasAvailableSettings"
[simplifiedMode]="simplifiedMode"></app-settings-share> [simplifiedMode]="simplifiedMode"></app-settings-share>
<app-settings-map #setting #map <app-settings-map #setting #map
[hidden]="!map.hasAvailableSettings" [hidden]="!map.HasAvailableSettings"
[simplifiedMode]="simplifiedMode"></app-settings-map> [simplifiedMode]="simplifiedMode"></app-settings-map>
<app-settings-meta-file #setting #metaFile <app-settings-meta-file #setting #metaFile
[hidden]="!metaFile.hasAvailableSettings" [hidden]="!metaFile.HasAvailableSettings"
[simplifiedMode]="simplifiedMode"></app-settings-meta-file> [simplifiedMode]="simplifiedMode"></app-settings-meta-file>
<app-settings-other #setting #other <app-settings-other #setting #other
[hidden]="!other.hasAvailableSettings" [hidden]="!other.HasAvailableSettings"
[simplifiedMode]="simplifiedMode"></app-settings-other> [simplifiedMode]="simplifiedMode"></app-settings-other>
<app-settings-random-photo #setting #random <app-settings-random-photo #setting #random
[hidden]="!random.hasAvailableSettings" [hidden]="!random.HasAvailableSettings"
[simplifiedMode]="simplifiedMode"></app-settings-random-photo> [simplifiedMode]="simplifiedMode"></app-settings-random-photo>
<app-settings-faces #setting #faces <app-settings-faces #setting #faces
[hidden]="!faces.hasAvailableSettings" [hidden]="!faces.HasAvailableSettings"
[simplifiedMode]="simplifiedMode"></app-settings-faces> [simplifiedMode]="simplifiedMode"></app-settings-faces>
<app-settings-indexing #setting #indexing <app-settings-indexing #setting #indexing
[hidden]="!indexing.hasAvailableSettings" [hidden]="!indexing.HasAvailableSettings"
[simplifiedMode]="simplifiedMode"></app-settings-indexing> [simplifiedMode]="simplifiedMode"></app-settings-indexing>
<app-settings-tasks #setting #tasks <app-settings-tasks #setting #tasks
[hidden]="!tasks.hasAvailableSettings" [hidden]="!tasks.HasAvailableSettings"
[simplifiedMode]="simplifiedMode"></app-settings-tasks> [simplifiedMode]="simplifiedMode"></app-settings-tasks>
</div> </div>
</div> </div>

View File

@ -1,5 +1,6 @@
export interface ISettingsComponent { export interface ISettingsComponent {
hasAvailableSettings: boolean; HasAvailableSettings: boolean;
Name: string; Name: string;
Changed: boolean;
} }

View File

@ -60,6 +60,14 @@ export abstract class SettingsComponent<T extends { [key: string]: any }, S exte
return this.name; return this.name;
} }
get Changed(): boolean {
return this.changed;
}
get HasAvailableSettings(): boolean {
return this.hasAvailableSettings;
}
onNewSettings = (s: IPrivateConfig) => { onNewSettings = (s: IPrivateConfig) => {
this.settings = Utils.clone(this.sliceFN(s)); this.settings = Utils.clone(this.sliceFN(s));
this.original = Utils.clone(this.settings); this.original = Utils.clone(this.settings);

View File

@ -18,7 +18,12 @@ import {UserRoles} from '../../../../../common/entities/UserDTO';
}) })
export class FacesSettingsComponent extends SettingsComponent<ClientConfig.FacesConfig> { export class FacesSettingsComponent extends SettingsComponent<ClientConfig.FacesConfig> {
public userRoles: Array<any> = []; public readonly userRoles = Utils
.enumToArray(UserRoles)
.filter(r => r.key !== UserRoles.LimitedGuest)
.filter(r => r.key <= this._authService.user.value.role)
.sort((a, b) => a.key - b.key);
constructor(_authService: AuthenticationService, constructor(_authService: AuthenticationService,
_navigation: NavigationService, _navigation: NavigationService,
_settingsService: FacesSettingsService, _settingsService: FacesSettingsService,
@ -26,11 +31,6 @@ export class FacesSettingsComponent extends SettingsComponent<ClientConfig.Faces
i18n: I18n) { i18n: I18n) {
super(i18n('Faces'), _authService, _navigation, _settingsService, notification, i18n, s => s.Client.Faces); super(i18n('Faces'), _authService, _navigation, _settingsService, notification, i18n, s => s.Client.Faces);
this.userRoles = Utils
.enumToArray(UserRoles)
.filter(r => r.key !== UserRoles.LimitedGuest)
.filter(r => r.key <= this._authService.user.value.role)
.sort((a, b) => a.key - b.key);
} }

View File

@ -99,43 +99,39 @@
<hr/> <hr/>
</ng-container> </ng-container>
<ng-container i18n>If you add a new folder to your gallery, the site indexes it automatically.</ng-container>&nbsp; <div class="alert alert-secondary" role="alert">
<ng-container i18n>If you would like to trigger indexing manually, click index button.</ng-container> <ng-container i18n>If you add a new folder to your gallery, the site indexes it automatically.</ng-container>&nbsp;
<br/> <ng-container i18n>If you would like to trigger indexing manually, click index button.</ng-container>
( <br/>
<ng-container i18n>Note: search only works among the indexed directories</ng-container> (<ng-container i18n>Note: search only works among the indexed directories</ng-container>)
)<br/> </div>
<div *ngIf="Progress != null"> <div *ngIf="Progress != null">
<app-settings-tasks-progress [progress]="Progress"></app-settings-tasks-progress> <app-settings-tasks-progress [progress]="Progress"></app-settings-tasks-progress>
</div> </div>
<div class="row justify-content-center buttons-row">
<button class="btn btn-success" <button class="btn btn-success ml-0"
*ngIf="Progress == null" *ngIf="Progress == null"
[disabled]="inProgress" [disabled]="inProgress"
title="Indexes the folders" title="Indexes the folders"
i18n-title i18n-title
(click)="index(false)" i18n>Index (click)="index()">
</button> <ng-container i18n>Index folders now</ng-container>
<button class="btn btn-primary" <span class="oi oi-media-play ml-2"></span>
title="Indexes the folders and also creates the thumbnails" </button>
i18n-title <button class="btn btn-secondary ml-0"
*ngIf="Progress == null" *ngIf="Progress != null"
[disabled]="inProgress" [disabled]="inProgress || Progress.state !== TaskState.running"
(click)="index(true)" i18n>Index with Thumbnails (click)="cancelIndexing()" i18n>Cancel converting
</button> </button>
<button class="btn btn-secondary" <button class="btn btn-danger ml-2"
*ngIf="Progress != null" [disabled]="inProgress"
[disabled]="inProgress || Progress.state !== TaskState.running" (click)="resetDatabase()" i18n>Reset Indexes
(click)="cancelIndexing()" i18n>Cancel </button>
</button>
<button class="btn btn-danger"
[disabled]="inProgress"
(click)="resetDatabase()" i18n>Reset Indexes
</button>
</div>
<hr/> <hr/>
<div class="row statics"> <div class="row statics">
<div class="col-md-4 col-12" i18n> <div class="col-md-4 col-12" i18n>

View File

@ -36,7 +36,7 @@ export class IndexingSettingsComponent extends SettingsComponent<ServerConfig.In
super(i18n('Folder indexing'), super(i18n('Folder indexing'),
_authService, _authService,
_navigation, _navigation,
<any>_settingsService, _settingsService,
notification, notification,
i18n, i18n,
s => s.Server.Indexing); s => s.Server.Indexing);
@ -89,11 +89,11 @@ export class IndexingSettingsComponent extends SettingsComponent<ServerConfig.In
} }
async index(createThumbnails: boolean) { async index() {
this.inProgress = true; this.inProgress = true;
this.error = ''; this.error = '';
try { try {
await this.tasksService.start(DefaultsTasks[DefaultsTasks.Indexing], {createThumbnails: !!createThumbnails}); await this.tasksService.start(DefaultsTasks[DefaultsTasks.Indexing]);
this.notification.info(this.i18n('Folder indexing started')); this.notification.info(this.i18n('Folder indexing started'));
this.inProgress = false; this.inProgress = false;
return true; return true;

View File

@ -26,7 +26,7 @@ export class MapSettingsComponent extends SettingsComponent<ClientConfig.MapConf
_settingsService: MapSettingsService, _settingsService: MapSettingsService,
notification: NotificationService, notification: NotificationService,
i18n: I18n) { i18n: I18n) {
super(i18n('Map'), _authService, _navigation, <any>_settingsService, notification, i18n, s => s.Client.Map); super(i18n('Map'), _authService, _navigation, _settingsService, notification, i18n, s => s.Client.Map);
this.mapProviders = Utils.enumToArray(ClientConfig.MapProviders); this.mapProviders = Utils.enumToArray(ClientConfig.MapProviders);
} }

View File

@ -22,7 +22,7 @@ export class MetaFileSettingsComponent extends SettingsComponent<ClientConfig.Me
_settingsService: MetaFileSettingsService, _settingsService: MetaFileSettingsService,
notification: NotificationService, notification: NotificationService,
i18n: I18n) { i18n: I18n) {
super(i18n('Meta file'), _authService, _navigation, <any>_settingsService, notification, i18n, s => s.Client.MetaFile); super(i18n('Meta file'), _authService, _navigation, _settingsService, notification, i18n, s => s.Client.MetaFile);
} }

View File

@ -21,7 +21,7 @@
[switch-on-text]="text.Enabled" [switch-on-text]="text.Enabled"
[switch-handle-width]="100" [switch-handle-width]="100"
[switch-label-width]="20" [switch-label-width]="20"
[(ngModel)]="settings.Server.enable"> [(ngModel)]="settings.Server.enabled">
</bSwitch> </bSwitch>
<small class="form-text text-muted" i18n>Runs directory scanning and thumbnail generation (only for Jimp) in a <small class="form-text text-muted" i18n>Runs directory scanning and thumbnail generation (only for Jimp) in a
different thread different thread
@ -29,7 +29,7 @@
</div> </div>
</div> </div>
<div class="form-group row" [hidden]="simplifiedMode || settings.Server.enable == false"> <div class="form-group row" [hidden]="simplifiedMode || settings.Server.enabled == false">
<label class="col-md-2 control-label" for="thumbnailThreads" i18n>Thumbnail threads</label> <label class="col-md-2 control-label" for="thumbnailThreads" i18n>Thumbnail threads</label>
<div class="col-md-10"> <div class="col-md-10">
<select id="thumbnailThreads" class="form-control" [(ngModel)]="settings.Server.thumbnailThreads" <select id="thumbnailThreads" class="form-control" [(ngModel)]="settings.Server.thumbnailThreads"

View File

@ -0,0 +1,4 @@
.buttons-row {
margin-top: 10px;
margin-bottom: 20px;
}

View File

@ -0,0 +1,129 @@
<form #settingsForm="ngForm" class="form-horizontal">
<div class="card mb-4">
<h5 class="card-header">{{Name}}<span *ngIf="changed">*</span></h5>
<div class="card-body">
<div [hidden]="!error" class="alert alert-danger" role="alert"><strong>Error: </strong>{{error}}</div>
<div [hidden]="settings.photoProcessingLibrary!=PhotoProcessingLib.Jimp"
class="alert alert-warning"
role="alert" i18n>It is highly recommended to use hardware accelerated (sharp or gm) lib for thumbnail
generation
</div>
<div [hidden]="simplifiedMode">
<div class="form-group row">
<label class="col-md-2 control-label" for="lib" i18n>Thumbnail generation library</label>
<div class="col-md-10">
<select id="lib" class="form-control" [(ngModel)]="settings.photoProcessingLibrary"
name="type" required>
<option *ngFor="let type of libTypes" [ngValue]="type.key">{{type.value}}
</option>
</select>
<small *ngIf="settings.photoProcessingLibrary==PhotoProcessingLib.sharp"
class="form-text text-muted" i18n>Make sure that sharp node module is installed (npm install sharp).
</small>
<small *ngIf="settings.photoProcessingLibrary==PhotoProcessingLib.gm"
class="form-text text-muted">
<ng-container i18n>Make sure that gm node module and</ng-container>
<a
href="http://www.graphicsmagick.org/" i18n>GraphicsMagick</a>
<ng-container i18n>are installed (npm install sharp).</ng-container>
</small>
</div>
</div>
<hr/>
</div>
<p class="title" i18n>Photo converting:</p>
<div class="form-group row">
<label class="col-md-2 control-label" for="enablePhotoConverting" i18n>Converting</label>
<div class="col-md-10">
<bSwitch
id="enablePhotoConverting"
class="switch"
name="enablePhotoConverting"
[switch-on-color]="'primary'"
[switch-inverse]="true"
[switch-off-text]="text.Disabled"
[switch-on-text]="text.Enabled"
[switch-handle-width]="100"
[switch-label-width]="20"
[(ngModel)]="settings.client.Converting.enabled">
</bSwitch>
<small class="form-text text-muted" i18n>Downsizes photos for faster preview loading. (Zooming in to the photo
loads the original)</small>
</div>
</div>
<div class="form-group row" [hidden]="!settings.client.Converting.enabled">
<label class="col-md-2 control-label" for="onTheFlyConverting" i18n>On the fly converting </label>
<div class="col-md-10">
<bSwitch
id="onTheFlyConverting"
class="switch"
name="onTheFlyConverting"
[switch-on-color]="'primary'"
[switch-inverse]="true"
[switch-off-text]="text.Disabled"
[switch-on-text]="text.Enabled"
[switch-handle-width]="100"
[switch-label-width]="20"
[(ngModel)]="settings.server.Converting.onTheFly">
</bSwitch>
<small class="form-text text-muted" i18n>Converts photos on the fly, when they are requested.</small>
</div>
</div>
<div class="form-group row" [hidden]="!settings.client.Converting.enabled">
<label class="col-md-2 control-label" for="resolution" i18n>Resolution</label>
<div class="col-md-10">
<select id="resolution" class="form-control" [(ngModel)]="settings.server.Converting.resolution"
name="resolution" required>
<option *ngFor="let resolution of resolutions" [ngValue]="resolution">{{resolution}}px
</option>
</select>
<small class="form-text text-muted" i18n>The shorter edge of the converted photo will be scaled down to this,
while
keeping the aspect ratio.</small>
</div>
</div>
<button class="btn btn-success float-right"
[disabled]="!settingsForm.form.valid || !changed || inProgress"
(click)="save()" i18n>Save
</button>
<button class="btn btn-secondary float-right"
[disabled]=" !changed || inProgress"
(click)="reset()" i18n>Reset
</button>
<div [hidden]="!settings.client.Converting.enabled">
<button class="btn btn-success float-left ml-0"
*ngIf="Progress == null"
[disabled]="inProgress"
title="Indexes the folders"
i18n-title
(click)="convertPhoto()">
<ng-container i18n>Convert photos now</ng-container>
<span class="oi oi-media-play ml-2"></span>
</button>
<button class="btn btn-secondary float-left ml-0"
*ngIf="Progress != null"
[disabled]="inProgress || Progress.state !== TaskState.running"
(click)="cancelPhotoConverting()" i18n>Cancel converting
</button>
<ng-container *ngIf="Progress != null">
<br/>
<hr/>
<app-settings-tasks-progress [progress]="Progress"></app-settings-tasks-progress>
</ng-container>
</div>
</div>
</div>
</form>

View File

@ -0,0 +1,105 @@
import {Component} from '@angular/core';
import {PhotoSettingsService} from './photo.settings.service';
import {SettingsComponent} from '../_abstract/abstract.settings.component';
import {AuthenticationService} from '../../../model/network/authentication.service';
import {NavigationService} from '../../../model/navigation.service';
import {NotificationService} from '../../../model/notification.service';
import {ClientConfig} from '../../../../../common/config/public/ConfigClass';
import {I18n} from '@ngx-translate/i18n-polyfill';
import {ScheduledTasksService} from '../scheduled-tasks.service';
import {ServerConfig} from '../../../../../common/config/private/IPrivateConfig';
import {Utils} from '../../../../../common/Utils';
import {DefaultsTasks} from '../../../../../common/entities/task/TaskDTO';
import {ErrorDTO} from '../../../../../common/entities/Error';
import {TaskState} from '../../../../../common/entities/settings/TaskProgressDTO';
@Component({
selector: 'app-settings-photo',
templateUrl: './photo.settings.component.html',
styleUrls: ['./photo.settings.component.css',
'../_abstract/abstract.settings.component.css'],
providers: [PhotoSettingsService],
})
export class PhotoSettingsComponent extends SettingsComponent<{
photoProcessingLibrary: ServerConfig.PhotoProcessingLib,
server: ServerConfig.PhotoConfig,
client: ClientConfig.PhotoConfig
}> {
resolutions = [720, 1080, 1440, 2160, 4320];
PhotoProcessingLib = ServerConfig.PhotoProcessingLib;
TaskState = TaskState;
libTypes = Utils
.enumToArray(ServerConfig.PhotoProcessingLib).map((v) => {
if (v.value.toLowerCase() === 'sharp') {
v.value += ' ' + this.i18n('(recommended)');
} else {
v.value += ' ' + this.i18n('(deprecated, will be removed)');
}
return v;
});
constructor(_authService: AuthenticationService,
_navigation: NavigationService,
_settingsService: PhotoSettingsService,
public tasksService: ScheduledTasksService,
notification: NotificationService,
i18n: I18n) {
super(i18n('Photo'), _authService, _navigation, _settingsService, notification, i18n, s => ({
photoProcessingLibrary: s.Server.Media.photoProcessingLibrary,
client: s.Client.Media.Photo,
server: s.Server.Media.Photo
}));
const currentRes = _settingsService.Settings.value.Server.Media.Photo.Converting.resolution;
if (this.resolutions.indexOf(currentRes) === -1) {
this.resolutions.push(currentRes);
}
}
get Progress() {
return this.tasksService.progress.value[DefaultsTasks[DefaultsTasks['Photo Converting']]];
}
async convertPhoto() {
this.inProgress = true;
this.error = '';
try {
await this.tasksService.start(DefaultsTasks[DefaultsTasks['Photo Converting']]);
this.notification.info(this.i18n('Photo converting started'));
this.inProgress = false;
return true;
} catch (err) {
console.log(err);
if (err.message) {
this.error = (<ErrorDTO>err).message;
}
}
this.inProgress = false;
return false;
}
async cancelPhotoConverting() {
this.inProgress = true;
this.error = '';
try {
await this.tasksService.stop(DefaultsTasks[DefaultsTasks['Photo Converting']]);
this.notification.info(this.i18n('Photo converting interrupted'));
this.inProgress = false;
return true;
} catch (err) {
console.log(err);
if (err.message) {
this.error = (<ErrorDTO>err).message;
}
}
this.inProgress = false;
return false;
}
}

View File

@ -0,0 +1,21 @@
import {Injectable} from '@angular/core';
import {NetworkService} from '../../../model/network/network.service';
import {ClientConfig} from '../../../../../common/config/public/ConfigClass';
import {SettingsService} from '../settings.service';
import {AbstractSettingsService} from '../_abstract/abstract.settings.service';
import {ServerConfig} from '../../../../../common/config/private/IPrivateConfig';
@Injectable()
export class PhotoSettingsService extends AbstractSettingsService<{ server: ServerConfig.PhotoConfig, client: ClientConfig.PhotoConfig }> {
constructor(private _networkService: NetworkService,
_settingsService: SettingsService) {
super(_settingsService);
}
public updateSettings(settings: { server: ServerConfig.PhotoConfig, client: ClientConfig.PhotoConfig }): Promise<void> {
return this._networkService.putJson('/settings/photo', {settings: settings});
}
}

View File

@ -2,7 +2,8 @@
<div class="card mb-4" <div class="card mb-4"
[ngClass]="settings.enabled && !_settingsService.isSupported()?'panel-warning':''"> [ngClass]="settings.enabled && !_settingsService.isSupported()?'panel-warning':''">
<h5 class="card-header"> <h5 class="card-header">
{{Name}}<ng-container *ngIf="changed">*</ng-container> {{Name}}
<ng-container *ngIf="changed">*</ng-container>
<div class="switch-wrapper"> <div class="switch-wrapper">
<bSwitch <bSwitch
class="switch" class="switch"
@ -22,7 +23,8 @@
<div [hidden]="!error" class="alert alert-danger" role="alert"><strong>Error: </strong>{{error}}</div> <div [hidden]="!error" class="alert alert-danger" role="alert"><strong>Error: </strong>{{error}}</div>
<ng-container *ngIf="settings.enabled || _settingsService.isSupported()"> <ng-container *ngIf="settings.enabled || _settingsService.isSupported()">
<div class="panel-info" i18n>
<div class="alert alert-secondary" role="alert" i18n>
This feature enables you to generate 'random photo' urls. This feature enables you to generate 'random photo' urls.
That URL returns a photo random selected from your gallery. That URL returns a photo random selected from your gallery.
You can use the url with 3rd party application like random changing desktop background. You can use the url with 3rd party application like random changing desktop background.

View File

@ -46,7 +46,7 @@ export class TasksSettingsComponent extends SettingsComponent<ServerConfig.TaskC
super(i18n('Tasks'), super(i18n('Tasks'),
_authService, _authService,
_navigation, _navigation,
<any>_settingsService, _settingsService,
notification, notification,
i18n, i18n,
s => s.Server.Tasks); s => s.Server.Tasks);

View File

@ -5,34 +5,7 @@
</h5> </h5>
<div class="card-body"> <div class="card-body">
<div [hidden]="!error" class="alert alert-danger" role="alert"><strong>Error: </strong>{{error}}</div> <div [hidden]="!error" class="alert alert-danger" role="alert"><strong>Error: </strong>{{error}}</div>
<div [hidden]="settings.server.processingLibrary!=ThumbnailProcessingLib.Jimp"
class="alert alert-warning"
role="alert" i18n>It is highly recommended to use hardware accelerated (sharp or gm) lib for thumbnail
generation
</div>
<fieldset>
<div class="form-group row">
<label class="col-md-2 control-label" for="lib" i18n>Thumbnail generation library</label>
<div class="col-md-10">
<select id="lib" class="form-control" [(ngModel)]="settings.server.processingLibrary"
name="type" required>
<option *ngFor="let type of types" [ngValue]="type.key">{{type.value}}
</option>
</select>
<small *ngIf="settings.server.processingLibrary==ThumbnailProcessingLib.sharp"
class="form-text text-muted" i18n>Make sure that sharp node module is installed (npm install sharp).
</small>
<small *ngIf="settings.server.processingLibrary==ThumbnailProcessingLib.gm"
class="form-text text-muted">
<ng-container i18n>Make sure that gm node module and</ng-container>
<a
href="http://www.graphicsmagick.org/" i18n>GraphicsMagick</a>
<ng-container i18n>are installed (npm install sharp).</ng-container>
</small>
</div>
</div>
<div class="form-group row" [hidden]="simplifiedMode"> <div class="form-group row" [hidden]="simplifiedMode">
<label class="col-md-2 control-label" for="quality" i18n>Thumbnail Quality</label> <label class="col-md-2 control-label" for="quality" i18n>Thumbnail Quality</label>
@ -84,7 +57,6 @@
</div> </div>
</div> </div>
</fieldset>
<button class="btn btn-success float-right" <button class="btn btn-success float-right"
[disabled]="!settingsForm.form.valid || !changed || inProgress" [disabled]="!settingsForm.form.valid || !changed || inProgress"

View File

@ -17,9 +17,8 @@ import {ServerConfig} from '../../../../../common/config/private/IPrivateConfig'
providers: [ThumbnailSettingsService], providers: [ThumbnailSettingsService],
}) })
export class ThumbnailSettingsComponent export class ThumbnailSettingsComponent
extends SettingsComponent<{ server: ServerConfig.ThumbnailConfig, client: ClientConfig.ThumbnailConfig }> extends SettingsComponent<{ server: ServerConfig.ThumbnailConfig, client: ClientConfig.ThumbnailConfig }>
implements OnInit { implements OnInit {
types: Array<any> = [];
ThumbnailProcessingLib: any; ThumbnailProcessingLib: any;
constructor(_authService: AuthenticationService, constructor(_authService: AuthenticationService,
@ -41,22 +40,12 @@ export class ThumbnailSettingsComponent
value = value.replace(new RegExp(',', 'g'), ';'); value = value.replace(new RegExp(',', 'g'), ';');
value = value.replace(new RegExp(' ', 'g'), ';'); value = value.replace(new RegExp(' ', 'g'), ';');
this.settings.client.thumbnailSizes = value.split(';') this.settings.client.thumbnailSizes = value.split(';')
.map(s => parseInt(s, 10)) .map(s => parseInt(s, 10))
.filter(i => !isNaN(i) && i > 0); .filter(i => !isNaN(i) && i > 0);
} }
ngOnInit() { ngOnInit() {
super.ngOnInit(); super.ngOnInit();
this.types = Utils
.enumToArray(ServerConfig.ThumbnailProcessingLib).map((v) => {
if (v.value.toLowerCase() === 'sharp') {
v.value += ' ' + this.i18n('(recommended)');
} else {
v.value += ' ' + this.i18n('(deprecated, will be removed)');
}
return v;
});
this.ThumbnailProcessingLib = ServerConfig.ThumbnailProcessingLib;
} }
} }

View File

@ -26,7 +26,8 @@ export class UserMangerSettingsComponent implements OnInit, ISettingsComponent {
public error: string = null; public error: string = null;
public inProgress = false; public inProgress = false;
Name: string; Name: string;
hasAvailableSettings = true; HasAvailableSettings = true;
Changed = false;
text = { text = {

View File

@ -1,7 +1,8 @@
<form #settingsForm="ngForm" class="form-horizontal"> <form #settingsForm="ngForm" class="form-horizontal">
<div class="card mb-4"> <div class="card mb-4">
<h5 class="card-header"> <h5 class="card-header">
{{Name}}<ng-container *ngIf="changed">*</ng-container> {{Name}}
<ng-container *ngIf="changed">*</ng-container>
<div class="switch-wrapper"> <div class="switch-wrapper">
<bSwitch <bSwitch
class="switch" class="switch"
@ -20,13 +21,27 @@
<div class="card-body"> <div class="card-body">
<div [hidden]="!error" class="alert alert-danger" role="alert"><strong>Error: </strong>{{error}}</div> <div [hidden]="!error" class="alert alert-danger" role="alert"><strong>Error: </strong>{{error}}</div>
<ng-container i18n>Video support uses ffmpeg. ffmpeg and ffprobe binaries need to be available in the PATH or the <div class="alert alert-secondary" role="alert">
@ffmpeg-installer/ffmpeg and @ffprobe-installer/ffprobe optional node packages need to be installed. <ng-container i18n>Video support uses ffmpeg. ffmpeg and ffprobe binaries need to be available in the PATH or
</ng-container> the
@ffmpeg-installer/ffmpeg and @ffprobe-installer/ffprobe optional node packages need to be installed.
</ng-container>
</div>
<hr/> <hr/>
<p class="title" i18n>Video transcoding:</p> <p class="title" i18n>Video transcoding:</p>
<div class="alert alert-secondary" role="alert">
<ng-container i18n>To ensure smooth video playback, video transcoding is recommended to a lower bit rate than
the
server's upload rate.
</ng-container>&nbsp;
<ng-container i18n>The transcoded videos will be save to the thumbnail folder.</ng-container>&nbsp;
<ng-container i18n>You can trigger the transcoding manually, but you can also create an automatic encoding task
in
advanced settings mode.
</ng-container>&nbsp;
</div>
<div class="form-group row" [hidden]="simplifiedMode"> <div class="form-group row" [hidden]="simplifiedMode">
<label class="col-md-2 control-label" for="format" i18n>Format</label> <label class="col-md-2 control-label" for="format" i18n>Format</label>
<div class="col-md-10"> <div class="col-md-10">
@ -109,28 +124,17 @@
(click)="reset()" i18n>Reset (click)="reset()" i18n>Reset
</button> </button>
<br/>
<hr/>
<ng-container i18n>To ensure smooth video playback, video transcoding is recommended to a lower bit rate than the <button class="btn btn-success float-left ml-0"
server's upload rate.
</ng-container>&nbsp;
<ng-container i18n>The transcoded videos will be save to the thumbnail folder.</ng-container>&nbsp;
<ng-container i18n>You can trigger the transcoding manually, but you can also create an automatic encoding task in
advanced settings mode.
</ng-container>&nbsp;
<br/>
<button class="btn btn-success float-right"
*ngIf="Progress == null" *ngIf="Progress == null"
[disabled]="inProgress" [disabled]="inProgress"
title="Indexes the folders" title="Indexes the folders"
i18n-title i18n-title
(click)="transcode()" i18n>Transcode videos now (click)="transcode()">
<ng-container i18n>Transcode videos now</ng-container>
<span class="oi oi-media-play ml-2"></span>
</button> </button>
<button class="btn btn-secondary float-right" <button class="btn btn-secondary float-left ml-0"
*ngIf="Progress != null" *ngIf="Progress != null"
[disabled]="inProgress || Progress.state !== TaskState.running" [disabled]="inProgress || Progress.state !== TaskState.running"
(click)="cancelTranscoding()" i18n>Cancel transcoding (click)="cancelTranscoding()" i18n>Cancel transcoding

View File

@ -35,10 +35,15 @@ export class VideoSettingsComponent extends SettingsComponent<{ server: ServerCo
public tasksService: ScheduledTasksService, public tasksService: ScheduledTasksService,
notification: NotificationService, notification: NotificationService,
i18n: I18n) { i18n: I18n) {
super(i18n('Video'), _authService, _navigation, <any>_settingsService, notification, i18n, s => ({ super(i18n('Video'), _authService, _navigation, _settingsService, notification, i18n, s => ({
client: s.Client.Media.Video, client: s.Client.Media.Video,
server: s.Server.Media.Video server: s.Server.Media.Video
})); }));
const currentRes = _settingsService.Settings.value.Server.Media.Video.transcoding.resolution;
if (this.resolutions.indexOf(currentRes) === -1) {
this.resolutions.push(currentRes);
}
} }

View File

@ -3,16 +3,17 @@ import {NetworkService} from '../../../model/network/network.service';
import {ClientConfig} from '../../../../../common/config/public/ConfigClass'; import {ClientConfig} from '../../../../../common/config/public/ConfigClass';
import {SettingsService} from '../settings.service'; import {SettingsService} from '../settings.service';
import {AbstractSettingsService} from '../_abstract/abstract.settings.service'; import {AbstractSettingsService} from '../_abstract/abstract.settings.service';
import {ServerConfig} from '../../../../../common/config/private/IPrivateConfig';
@Injectable() @Injectable()
export class VideoSettingsService extends AbstractSettingsService<ClientConfig.MapConfig> { export class VideoSettingsService extends AbstractSettingsService<{ server: ServerConfig.VideoConfig, client: ClientConfig.VideoConfig }> {
constructor(private _networkService: NetworkService, constructor(private _networkService: NetworkService,
_settingsService: SettingsService) { _settingsService: SettingsService) {
super(_settingsService); super(_settingsService);
} }
public updateSettings(settings: ClientConfig.VideoConfig): Promise<void> { public updateSettings(settings: { server: ServerConfig.VideoConfig, client: ClientConfig.VideoConfig }): Promise<void> {
return this._networkService.putJson('/settings/video', {settings: settings}); return this._networkService.putJson('/settings/video', {settings: settings});
} }