improve settings
This commit is contained in:
parent
95b06ffc63
commit
5160e19a35
@ -359,7 +359,7 @@ export class SettingsMWs {
|
||||
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.enabled = settings.Server.enabled;
|
||||
original.Server.Threading.thumbnailThreads = settings.Server.thumbnailThreads;
|
||||
original.save();
|
||||
await ConfigDiagnostics.runDiagnostics();
|
||||
|
||||
@ -11,7 +11,7 @@ export class DiskManager {
|
||||
static threadPool: DiskManagerTH = null;
|
||||
|
||||
public static init() {
|
||||
if (Config.Server.Threading.enable === true) {
|
||||
if (Config.Server.Threading.enabled === true) {
|
||||
DiskManager.threadPool = new DiskManagerTH(1);
|
||||
}
|
||||
}
|
||||
@ -23,7 +23,7 @@ export class DiskManager {
|
||||
|
||||
let directory: DirectoryDTO;
|
||||
|
||||
if (Config.Server.Threading.enable === true) {
|
||||
if (Config.Server.Threading.enabled === true) {
|
||||
directory = await DiskManager.threadPool.execute(relativeDirectoryName, settings);
|
||||
} else {
|
||||
directory = await DiskMangerWorker.scanDirectory(relativeDirectoryName, settings);
|
||||
|
||||
@ -81,13 +81,13 @@ export class ConfigDiagnostics {
|
||||
}
|
||||
}
|
||||
|
||||
static async testThumbnailLib(processingLibrary: ServerConfig.ThumbnailProcessingLib) {
|
||||
static async testThumbnailLib(processingLibrary: ServerConfig.PhotoProcessingLib) {
|
||||
switch (processingLibrary) {
|
||||
case ServerConfig.ThumbnailProcessingLib.sharp:
|
||||
case ServerConfig.PhotoProcessingLib.sharp:
|
||||
const sharp = require('sharp');
|
||||
sharp();
|
||||
break;
|
||||
case ServerConfig.ThumbnailProcessingLib.gm:
|
||||
case ServerConfig.PhotoProcessingLib.gm:
|
||||
const gm = require('gm');
|
||||
await new Promise((resolve, reject) => {
|
||||
gm(ProjectPath.FrontendFolder + '/assets/icon.png').size((err: Error) => {
|
||||
@ -120,8 +120,10 @@ export class ConfigDiagnostics {
|
||||
}
|
||||
|
||||
|
||||
static async testServerThumbnailConfig(thumbnailConfig: ServerConfig.ThumbnailConfig) {
|
||||
await ConfigDiagnostics.testThumbnailLib(thumbnailConfig.processingLibrary);
|
||||
public static async testServerThumbnailConfig(server: ServerConfig.ThumbnailConfig) {
|
||||
if (server.personFaceMargin < 0 || server.personFaceMargin > 1) {
|
||||
throw new Error('personFaceMargin should be between 0 and 1');
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
await ConfigDiagnostics.testThumbnailLib(Config.Server.Media.Thumbnail.processingLibrary);
|
||||
await ConfigDiagnostics.testThumbnailLib(Config.Server.Media.photoProcessingLibrary);
|
||||
} catch (ex) {
|
||||
const err: Error = ex;
|
||||
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());
|
||||
Logger.warn(LOG_TAG, '[Thumbnail hardware acceleration] module error: ', err.toString());
|
||||
Logger.warn(LOG_TAG, 'Thumbnail hardware acceleration is not possible.' +
|
||||
' \'' + ServerConfig.ThumbnailProcessingLib[Config.Server.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');
|
||||
Config.Server.Media.Thumbnail.processingLibrary = ServerConfig.ThumbnailProcessingLib.Jimp;
|
||||
Config.Server.Media.photoProcessingLibrary = ServerConfig.PhotoProcessingLib.Jimp;
|
||||
}
|
||||
}
|
||||
|
||||
@ -343,4 +345,5 @@ export class ConfigDiagnostics {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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) {
|
||||
Config.Client.Media.Thumbnail.concurrentThumbnailGenerations = Config.Server.Threading.thumbnailThreads;
|
||||
} else {
|
||||
@ -32,12 +32,12 @@ export class PhotoProcessing {
|
||||
Config.Client.Media.Thumbnail.concurrentThumbnailGenerations = 1;
|
||||
}
|
||||
|
||||
if (Config.Server.Threading.enable === true &&
|
||||
Config.Server.Media.Thumbnail.processingLibrary === ServerConfig.ThumbnailProcessingLib.Jimp) {
|
||||
if (Config.Server.Threading.enabled === true &&
|
||||
Config.Server.Media.photoProcessingLibrary === ServerConfig.PhotoProcessingLib.Jimp) {
|
||||
this.taskQue = new ThumbnailTH(Config.Client.Media.Thumbnail.concurrentThumbnailGenerations);
|
||||
} else {
|
||||
this.taskQue = new TaskExecuter(Config.Client.Media.Thumbnail.concurrentThumbnailGenerations,
|
||||
(input => ThumbnailWorker.render(input, Config.Server.Media.Thumbnail.processingLibrary)));
|
||||
(input => ThumbnailWorker.render(input, Config.Server.Media.photoProcessingLibrary)));
|
||||
}
|
||||
|
||||
this.initDone = true;
|
||||
|
||||
@ -2,6 +2,7 @@ import {ITask} from './tasks/ITask';
|
||||
import {IndexingTask} from './tasks/IndexingTask';
|
||||
import {DBRestTask} from './tasks/DBResetTask';
|
||||
import {VideoConvertingTask} from './tasks/VideoConvertingTask';
|
||||
import {PhotoConvertingTask} from './tasks/PhotoConvertingTask';
|
||||
|
||||
export class TaskRepository {
|
||||
|
||||
@ -28,3 +29,4 @@ export class TaskRepository {
|
||||
TaskRepository.Instance.register(new IndexingTask());
|
||||
TaskRepository.Instance.register(new DBRestTask());
|
||||
TaskRepository.Instance.register(new VideoConvertingTask());
|
||||
TaskRepository.Instance.register(new PhotoConvertingTask());
|
||||
|
||||
@ -2,17 +2,15 @@ import {TaskProgressDTO, TaskState} from '../../../../common/entities/settings/T
|
||||
import {ConfigTemplateEntry} from '../../../../common/entities/task/TaskDTO';
|
||||
import {Task} from './Task';
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs';
|
||||
import * as util from 'util';
|
||||
import {DiskManager} from '../../DiskManger';
|
||||
import {DiskMangerWorker} from '../../threading/DiskMangerWorker';
|
||||
import {DirectoryDTO} from '../../../../common/entities/DirectoryDTO';
|
||||
import {Logger} from '../../../Logger';
|
||||
|
||||
declare var global: NodeJS.Global;
|
||||
|
||||
|
||||
const LOG_TAG = '[FileTask]';
|
||||
const existsPr = util.promisify(fs.exists);
|
||||
|
||||
|
||||
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.progress++;
|
||||
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;
|
||||
}
|
||||
|
||||
@ -1,30 +1,18 @@
|
||||
import {TaskProgressDTO, TaskState} 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 {Task} from './Task';
|
||||
import {ConfigTemplateEntry, DefaultsTasks} from '../../../../common/entities/task/TaskDTO';
|
||||
import {ServerConfig} from '../../../../common/config/private/IPrivateConfig';
|
||||
import {PhotoProcessing} from '../../fileprocessing/PhotoProcessing';
|
||||
|
||||
declare var global: NodeJS.Global;
|
||||
const LOG_TAG = '[IndexingTask]';
|
||||
|
||||
export class IndexingTask extends Task<{ createThumbnails: boolean }> {
|
||||
export class IndexingTask extends Task {
|
||||
public readonly Name = DefaultsTasks[DefaultsTasks.Indexing];
|
||||
directoriesToIndex: string[] = [];
|
||||
public readonly ConfigTemplate: ConfigTemplateEntry[] = [{
|
||||
id: 'createThumbnails',
|
||||
type: 'boolean',
|
||||
name: 'With thumbnails',
|
||||
defaultValue: false
|
||||
}];
|
||||
public readonly ConfigTemplate: ConfigTemplateEntry[] = null;
|
||||
|
||||
public get Supported(): boolean {
|
||||
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++) {
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@ -14,14 +14,14 @@ const existsPr = util.promisify(fs.exists);
|
||||
|
||||
|
||||
export class PhotoConvertingTask extends FileTask<string> {
|
||||
public readonly Name = DefaultsTasks[DefaultsTasks['Video Converting']];
|
||||
public readonly Name = DefaultsTasks[DefaultsTasks['Photo Converting']];
|
||||
|
||||
constructor() {
|
||||
super({noVideo: true, noMetaFile: true});
|
||||
}
|
||||
|
||||
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[]> {
|
||||
@ -40,7 +40,7 @@ export class PhotoConvertingTask extends FileTask<string> {
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -103,7 +103,7 @@ export class ThumbnailTH extends ThreadPool<void> implements ITaskExecuter<Rende
|
||||
return super.executeTask(<ThumbnailTask>{
|
||||
type: WorkerTaskTypes.thumbnail,
|
||||
input: input,
|
||||
renderer: Config.Server.Media.Thumbnail.processingLibrary
|
||||
renderer: Config.Server.Media.photoProcessingLibrary
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -9,16 +9,16 @@ export class ThumbnailWorker {
|
||||
|
||||
private static imageRenderer: (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) {
|
||||
return this.renderFromImage(input, renderer);
|
||||
}
|
||||
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) {
|
||||
ThumbnailWorker.imageRenderer = ImageRendererFactory.build(renderer);
|
||||
ThumbnailWorker.rendererType = renderer;
|
||||
@ -117,13 +117,13 @@ export class VideoRendererFactory {
|
||||
|
||||
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) {
|
||||
case ServerConfig.ThumbnailProcessingLib.Jimp:
|
||||
case ServerConfig.PhotoProcessingLib.Jimp:
|
||||
return ImageRendererFactory.Jimp();
|
||||
case ServerConfig.ThumbnailProcessingLib.gm:
|
||||
case ServerConfig.PhotoProcessingLib.gm:
|
||||
return ImageRendererFactory.Gm();
|
||||
case ServerConfig.ThumbnailProcessingLib.sharp:
|
||||
case ServerConfig.PhotoProcessingLib.sharp:
|
||||
return ImageRendererFactory.Sharp();
|
||||
}
|
||||
throw new Error('unknown renderer');
|
||||
|
||||
@ -54,7 +54,7 @@ export interface DiskManagerTask extends WorkerTask {
|
||||
|
||||
export interface ThumbnailTask extends WorkerTask {
|
||||
input: RendererInput;
|
||||
renderer: ServerConfig.ThumbnailProcessingLib;
|
||||
renderer: ServerConfig.PhotoProcessingLib;
|
||||
}
|
||||
|
||||
export module WorkerTask {
|
||||
|
||||
@ -14,7 +14,7 @@ export module ServerConfig {
|
||||
none = 1, error = 2, all = 3
|
||||
}
|
||||
|
||||
export enum ThumbnailProcessingLib {
|
||||
export enum PhotoProcessingLib {
|
||||
sharp = 3,
|
||||
Jimp = 1,
|
||||
gm = 2,
|
||||
@ -43,7 +43,6 @@ export module ServerConfig {
|
||||
}
|
||||
|
||||
export interface ThumbnailConfig {
|
||||
processingLibrary: ThumbnailProcessingLib;
|
||||
qualityPriority: boolean;
|
||||
personFaceMargin: number; // in ration [0-1]
|
||||
}
|
||||
@ -65,7 +64,7 @@ export module ServerConfig {
|
||||
}
|
||||
|
||||
export interface ThreadingConfig {
|
||||
enable: boolean;
|
||||
enabled: boolean;
|
||||
thumbnailThreads: number;
|
||||
}
|
||||
|
||||
@ -107,6 +106,7 @@ export module ServerConfig {
|
||||
export interface MediaConfig {
|
||||
folder: string;
|
||||
tempFolder: string;
|
||||
photoProcessingLibrary: PhotoProcessingLib;
|
||||
Video: VideoConfig;
|
||||
Photo: PhotoConfig;
|
||||
Thumbnail: ThumbnailConfig;
|
||||
|
||||
@ -14,8 +14,8 @@ export class PrivateConfigDefaultsClass extends PublicConfigClass implements IPr
|
||||
Media: {
|
||||
folder: 'demo/images',
|
||||
tempFolder: 'demo/tmp',
|
||||
photoProcessingLibrary: ServerConfig.PhotoProcessingLib.sharp,
|
||||
Thumbnail: {
|
||||
processingLibrary: ServerConfig.ThumbnailProcessingLib.sharp,
|
||||
qualityPriority: true,
|
||||
personFaceMargin: 0.6
|
||||
},
|
||||
@ -61,7 +61,7 @@ export class PrivateConfigDefaultsClass extends PublicConfigClass implements IPr
|
||||
updateTimeout: 1000 * 60 * 5
|
||||
},
|
||||
Threading: {
|
||||
enable: true,
|
||||
enabled: true,
|
||||
thumbnailThreads: 0
|
||||
},
|
||||
Indexing: {
|
||||
|
||||
@ -2,7 +2,7 @@ export type fieldType = 'string' | 'number' | 'boolean';
|
||||
|
||||
|
||||
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 {
|
||||
|
||||
@ -87,6 +87,7 @@ import {TimepickerModule} from 'ngx-bootstrap/timepicker';
|
||||
import {TimeStampDatePickerComponent} from './ui/utils/timestamp-datepicker/datepicker.component';
|
||||
import {TimeStampTimePickerComponent} from './ui/utils/timestamp-timepicker/timepicker.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,
|
||||
ThumbnailSettingsComponent,
|
||||
VideoSettingsComponent,
|
||||
PhotoSettingsComponent,
|
||||
MetaFileSettingsComponent,
|
||||
SearchSettingsComponent,
|
||||
ShareSettingsComponent,
|
||||
|
||||
@ -53,8 +53,10 @@
|
||||
<button class="btn btn-link nav-link text-md-left py-md-1 px-md-0"
|
||||
*ngFor="let s of contents; let i=index;"
|
||||
(click)="scrollTo(i)"
|
||||
[hidden]="!s.hasAvailableSettings"
|
||||
>{{s.Name}}</button>
|
||||
[hidden]="!s.HasAvailableSettings">
|
||||
{{s.Name}}<!--
|
||||
--><ng-container *ngIf="s.Changed">*</ng-container>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -63,44 +65,47 @@
|
||||
<div class="col-md-10">
|
||||
<app-settings-basic #setting #basic
|
||||
[simplifiedMode]="simplifiedMode"
|
||||
[hidden]="!basic.hasAvailableSettings"></app-settings-basic>
|
||||
[hidden]="!basic.HasAvailableSettings"></app-settings-basic>
|
||||
<app-settings-usermanager #setting #userManager
|
||||
[hidden]="!userManager.hasAvailableSettings"></app-settings-usermanager>
|
||||
[hidden]="!userManager.HasAvailableSettings"></app-settings-usermanager>
|
||||
<app-settings-database #setting #database
|
||||
[simplifiedMode]="simplifiedMode"
|
||||
[hidden]="!database.hasAvailableSettings"></app-settings-database>
|
||||
<app-settings-thumbnail #setting #thumbnail
|
||||
[hidden]="!thumbnail.hasAvailableSettings"
|
||||
[simplifiedMode]="simplifiedMode"></app-settings-thumbnail>
|
||||
[hidden]="!database.HasAvailableSettings"></app-settings-database>
|
||||
<app-settings-photo #setting #photo
|
||||
[hidden]="!photo.HasAvailableSettings"
|
||||
[simplifiedMode]="simplifiedMode"></app-settings-photo>
|
||||
<app-settings-video #setting #video
|
||||
[hidden]="!video.hasAvailableSettings"
|
||||
[hidden]="!video.HasAvailableSettings"
|
||||
[simplifiedMode]="simplifiedMode"></app-settings-video>
|
||||
<app-settings-thumbnail #setting #thumbnail
|
||||
[hidden]="!thumbnail.HasAvailableSettings"
|
||||
[simplifiedMode]="simplifiedMode"></app-settings-thumbnail>
|
||||
<app-settings-search #setting #search
|
||||
[hidden]="!search.hasAvailableSettings"
|
||||
[hidden]="!search.HasAvailableSettings"
|
||||
[simplifiedMode]="simplifiedMode"></app-settings-search>
|
||||
<app-settings-share #setting #share
|
||||
[hidden]="!share.hasAvailableSettings"
|
||||
[hidden]="!share.HasAvailableSettings"
|
||||
[simplifiedMode]="simplifiedMode"></app-settings-share>
|
||||
<app-settings-map #setting #map
|
||||
[hidden]="!map.hasAvailableSettings"
|
||||
[hidden]="!map.HasAvailableSettings"
|
||||
[simplifiedMode]="simplifiedMode"></app-settings-map>
|
||||
<app-settings-meta-file #setting #metaFile
|
||||
[hidden]="!metaFile.hasAvailableSettings"
|
||||
[hidden]="!metaFile.HasAvailableSettings"
|
||||
[simplifiedMode]="simplifiedMode"></app-settings-meta-file>
|
||||
<app-settings-other #setting #other
|
||||
[hidden]="!other.hasAvailableSettings"
|
||||
[hidden]="!other.HasAvailableSettings"
|
||||
[simplifiedMode]="simplifiedMode"></app-settings-other>
|
||||
<app-settings-random-photo #setting #random
|
||||
[hidden]="!random.hasAvailableSettings"
|
||||
[hidden]="!random.HasAvailableSettings"
|
||||
[simplifiedMode]="simplifiedMode"></app-settings-random-photo>
|
||||
<app-settings-faces #setting #faces
|
||||
[hidden]="!faces.hasAvailableSettings"
|
||||
[hidden]="!faces.HasAvailableSettings"
|
||||
[simplifiedMode]="simplifiedMode"></app-settings-faces>
|
||||
<app-settings-indexing #setting #indexing
|
||||
[hidden]="!indexing.hasAvailableSettings"
|
||||
[hidden]="!indexing.HasAvailableSettings"
|
||||
[simplifiedMode]="simplifiedMode"></app-settings-indexing>
|
||||
<app-settings-tasks #setting #tasks
|
||||
[hidden]="!tasks.hasAvailableSettings"
|
||||
[hidden]="!tasks.HasAvailableSettings"
|
||||
[simplifiedMode]="simplifiedMode"></app-settings-tasks>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
export interface ISettingsComponent {
|
||||
|
||||
hasAvailableSettings: boolean;
|
||||
HasAvailableSettings: boolean;
|
||||
Name: string;
|
||||
Changed: boolean;
|
||||
}
|
||||
|
||||
@ -60,6 +60,14 @@ export abstract class SettingsComponent<T extends { [key: string]: any }, S exte
|
||||
return this.name;
|
||||
}
|
||||
|
||||
get Changed(): boolean {
|
||||
return this.changed;
|
||||
}
|
||||
|
||||
get HasAvailableSettings(): boolean {
|
||||
return this.hasAvailableSettings;
|
||||
}
|
||||
|
||||
onNewSettings = (s: IPrivateConfig) => {
|
||||
this.settings = Utils.clone(this.sliceFN(s));
|
||||
this.original = Utils.clone(this.settings);
|
||||
|
||||
@ -18,7 +18,12 @@ import {UserRoles} from '../../../../../common/entities/UserDTO';
|
||||
})
|
||||
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,
|
||||
_navigation: NavigationService,
|
||||
_settingsService: FacesSettingsService,
|
||||
@ -26,11 +31,6 @@ export class FacesSettingsComponent extends SettingsComponent<ClientConfig.Faces
|
||||
i18n: I18n) {
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -99,43 +99,39 @@
|
||||
<hr/>
|
||||
</ng-container>
|
||||
|
||||
<ng-container i18n>If you add a new folder to your gallery, the site indexes it automatically.</ng-container>
|
||||
<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>
|
||||
)<br/>
|
||||
<div class="alert alert-secondary" role="alert">
|
||||
<ng-container i18n>If you add a new folder to your gallery, the site indexes it automatically.</ng-container>
|
||||
<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>)
|
||||
</div>
|
||||
|
||||
|
||||
<div *ngIf="Progress != null">
|
||||
<app-settings-tasks-progress [progress]="Progress"></app-settings-tasks-progress>
|
||||
</div>
|
||||
|
||||
<div class="row justify-content-center buttons-row">
|
||||
<button class="btn btn-success"
|
||||
*ngIf="Progress == null"
|
||||
[disabled]="inProgress"
|
||||
title="Indexes the folders"
|
||||
i18n-title
|
||||
(click)="index(false)" i18n>Index
|
||||
</button>
|
||||
<button class="btn btn-primary"
|
||||
title="Indexes the folders and also creates the thumbnails"
|
||||
i18n-title
|
||||
*ngIf="Progress == null"
|
||||
[disabled]="inProgress"
|
||||
(click)="index(true)" i18n>Index with Thumbnails
|
||||
</button>
|
||||
<button class="btn btn-secondary"
|
||||
*ngIf="Progress != null"
|
||||
[disabled]="inProgress || Progress.state !== TaskState.running"
|
||||
(click)="cancelIndexing()" i18n>Cancel
|
||||
</button>
|
||||
<button class="btn btn-danger"
|
||||
[disabled]="inProgress"
|
||||
(click)="resetDatabase()" i18n>Reset Indexes
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<button class="btn btn-success ml-0"
|
||||
*ngIf="Progress == null"
|
||||
[disabled]="inProgress"
|
||||
title="Indexes the folders"
|
||||
i18n-title
|
||||
(click)="index()">
|
||||
<ng-container i18n>Index folders now</ng-container>
|
||||
<span class="oi oi-media-play ml-2"></span>
|
||||
</button>
|
||||
<button class="btn btn-secondary ml-0"
|
||||
*ngIf="Progress != null"
|
||||
[disabled]="inProgress || Progress.state !== TaskState.running"
|
||||
(click)="cancelIndexing()" i18n>Cancel converting
|
||||
</button>
|
||||
<button class="btn btn-danger ml-2"
|
||||
[disabled]="inProgress"
|
||||
(click)="resetDatabase()" i18n>Reset Indexes
|
||||
</button>
|
||||
|
||||
|
||||
<hr/>
|
||||
<div class="row statics">
|
||||
<div class="col-md-4 col-12" i18n>
|
||||
|
||||
@ -36,7 +36,7 @@ export class IndexingSettingsComponent extends SettingsComponent<ServerConfig.In
|
||||
super(i18n('Folder indexing'),
|
||||
_authService,
|
||||
_navigation,
|
||||
<any>_settingsService,
|
||||
_settingsService,
|
||||
notification,
|
||||
i18n,
|
||||
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.error = '';
|
||||
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.inProgress = false;
|
||||
return true;
|
||||
|
||||
@ -26,7 +26,7 @@ export class MapSettingsComponent extends SettingsComponent<ClientConfig.MapConf
|
||||
_settingsService: MapSettingsService,
|
||||
notification: NotificationService,
|
||||
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);
|
||||
}
|
||||
|
||||
@ -22,7 +22,7 @@ export class MetaFileSettingsComponent extends SettingsComponent<ClientConfig.Me
|
||||
_settingsService: MetaFileSettingsService,
|
||||
notification: NotificationService,
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -21,7 +21,7 @@
|
||||
[switch-on-text]="text.Enabled"
|
||||
[switch-handle-width]="100"
|
||||
[switch-label-width]="20"
|
||||
[(ngModel)]="settings.Server.enable">
|
||||
[(ngModel)]="settings.Server.enabled">
|
||||
</bSwitch>
|
||||
<small class="form-text text-muted" i18n>Runs directory scanning and thumbnail generation (only for Jimp) in a
|
||||
different thread
|
||||
@ -29,7 +29,7 @@
|
||||
</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>
|
||||
<div class="col-md-10">
|
||||
<select id="thumbnailThreads" class="form-control" [(ngModel)]="settings.Server.thumbnailThreads"
|
||||
|
||||
@ -0,0 +1,4 @@
|
||||
.buttons-row {
|
||||
margin-top: 10px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
129
src/frontend/app/ui/settings/photo/photo.settings.component.html
Normal file
129
src/frontend/app/ui/settings/photo/photo.settings.component.html
Normal 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>
|
||||
105
src/frontend/app/ui/settings/photo/photo.settings.component.ts
Normal file
105
src/frontend/app/ui/settings/photo/photo.settings.component.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
21
src/frontend/app/ui/settings/photo/photo.settings.service.ts
Normal file
21
src/frontend/app/ui/settings/photo/photo.settings.service.ts
Normal 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});
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@ -2,7 +2,8 @@
|
||||
<div class="card mb-4"
|
||||
[ngClass]="settings.enabled && !_settingsService.isSupported()?'panel-warning':''">
|
||||
<h5 class="card-header">
|
||||
{{Name}}<ng-container *ngIf="changed">*</ng-container>
|
||||
{{Name}}
|
||||
<ng-container *ngIf="changed">*</ng-container>
|
||||
<div class="switch-wrapper">
|
||||
<bSwitch
|
||||
class="switch"
|
||||
@ -22,7 +23,8 @@
|
||||
<div [hidden]="!error" class="alert alert-danger" role="alert"><strong>Error: </strong>{{error}}</div>
|
||||
|
||||
<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.
|
||||
That URL returns a photo random selected from your gallery.
|
||||
You can use the url with 3rd party application like random changing desktop background.
|
||||
|
||||
@ -46,7 +46,7 @@ export class TasksSettingsComponent extends SettingsComponent<ServerConfig.TaskC
|
||||
super(i18n('Tasks'),
|
||||
_authService,
|
||||
_navigation,
|
||||
<any>_settingsService,
|
||||
_settingsService,
|
||||
notification,
|
||||
i18n,
|
||||
s => s.Server.Tasks);
|
||||
|
||||
@ -5,34 +5,7 @@
|
||||
</h5>
|
||||
<div class="card-body">
|
||||
<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">
|
||||
<label class="col-md-2 control-label" for="quality" i18n>Thumbnail Quality</label>
|
||||
@ -84,7 +57,6 @@
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<button class="btn btn-success float-right"
|
||||
[disabled]="!settingsForm.form.valid || !changed || inProgress"
|
||||
|
||||
@ -17,9 +17,8 @@ import {ServerConfig} from '../../../../../common/config/private/IPrivateConfig'
|
||||
providers: [ThumbnailSettingsService],
|
||||
})
|
||||
export class ThumbnailSettingsComponent
|
||||
extends SettingsComponent<{ server: ServerConfig.ThumbnailConfig, client: ClientConfig.ThumbnailConfig }>
|
||||
implements OnInit {
|
||||
types: Array<any> = [];
|
||||
extends SettingsComponent<{ server: ServerConfig.ThumbnailConfig, client: ClientConfig.ThumbnailConfig }>
|
||||
implements OnInit {
|
||||
ThumbnailProcessingLib: any;
|
||||
|
||||
constructor(_authService: AuthenticationService,
|
||||
@ -41,22 +40,12 @@ export class ThumbnailSettingsComponent
|
||||
value = value.replace(new RegExp(',', 'g'), ';');
|
||||
value = value.replace(new RegExp(' ', 'g'), ';');
|
||||
this.settings.client.thumbnailSizes = value.split(';')
|
||||
.map(s => parseInt(s, 10))
|
||||
.filter(i => !isNaN(i) && i > 0);
|
||||
.map(s => parseInt(s, 10))
|
||||
.filter(i => !isNaN(i) && i > 0);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -26,7 +26,8 @@ export class UserMangerSettingsComponent implements OnInit, ISettingsComponent {
|
||||
public error: string = null;
|
||||
public inProgress = false;
|
||||
Name: string;
|
||||
hasAvailableSettings = true;
|
||||
HasAvailableSettings = true;
|
||||
Changed = false;
|
||||
|
||||
|
||||
text = {
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
<form #settingsForm="ngForm" class="form-horizontal">
|
||||
<div class="card mb-4">
|
||||
<h5 class="card-header">
|
||||
{{Name}}<ng-container *ngIf="changed">*</ng-container>
|
||||
{{Name}}
|
||||
<ng-container *ngIf="changed">*</ng-container>
|
||||
<div class="switch-wrapper">
|
||||
<bSwitch
|
||||
class="switch"
|
||||
@ -20,13 +21,27 @@
|
||||
<div class="card-body">
|
||||
<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
|
||||
@ffmpeg-installer/ffmpeg and @ffprobe-installer/ffprobe optional node packages need to be installed.
|
||||
</ng-container>
|
||||
|
||||
<div class="alert alert-secondary" role="alert">
|
||||
<ng-container i18n>Video support uses ffmpeg. ffmpeg and ffprobe binaries need to be available in the PATH or
|
||||
the
|
||||
@ffmpeg-installer/ffmpeg and @ffprobe-installer/ffprobe optional node packages need to be installed.
|
||||
</ng-container>
|
||||
</div>
|
||||
<hr/>
|
||||
|
||||
<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>
|
||||
<ng-container i18n>The transcoded videos will be save to the thumbnail folder.</ng-container>
|
||||
<ng-container i18n>You can trigger the transcoding manually, but you can also create an automatic encoding task
|
||||
in
|
||||
advanced settings mode.
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
<div class="form-group row" [hidden]="simplifiedMode">
|
||||
<label class="col-md-2 control-label" for="format" i18n>Format</label>
|
||||
<div class="col-md-10">
|
||||
@ -109,28 +124,17 @@
|
||||
(click)="reset()" i18n>Reset
|
||||
</button>
|
||||
|
||||
<br/>
|
||||
<hr/>
|
||||
|
||||
<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>
|
||||
<ng-container i18n>The transcoded videos will be save to the thumbnail folder.</ng-container>
|
||||
<ng-container i18n>You can trigger the transcoding manually, but you can also create an automatic encoding task in
|
||||
advanced settings mode.
|
||||
</ng-container>
|
||||
|
||||
<br/>
|
||||
|
||||
|
||||
<button class="btn btn-success float-right"
|
||||
<button class="btn btn-success float-left ml-0"
|
||||
*ngIf="Progress == null"
|
||||
[disabled]="inProgress"
|
||||
title="Indexes the folders"
|
||||
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 class="btn btn-secondary float-right"
|
||||
<button class="btn btn-secondary float-left ml-0"
|
||||
*ngIf="Progress != null"
|
||||
[disabled]="inProgress || Progress.state !== TaskState.running"
|
||||
(click)="cancelTranscoding()" i18n>Cancel transcoding
|
||||
|
||||
@ -35,10 +35,15 @@ export class VideoSettingsComponent extends SettingsComponent<{ server: ServerCo
|
||||
public tasksService: ScheduledTasksService,
|
||||
notification: NotificationService,
|
||||
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,
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -3,16 +3,17 @@ 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 VideoSettingsService extends AbstractSettingsService<ClientConfig.MapConfig> {
|
||||
export class VideoSettingsService extends AbstractSettingsService<{ server: ServerConfig.VideoConfig, client: ClientConfig.VideoConfig }> {
|
||||
constructor(private _networkService: NetworkService,
|
||||
_settingsService: 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});
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user