mirror of
				https://github.com/KevinMidboe/immich.git
				synced 2025-10-29 17:40:28 +00:00 
			
		
		
		
	* feat(server/web): webp thumbnail size configurable * update api * add ui and fix test * lint * setting for jpeg size * feat: coerce to number * api * jpeg resolution --------- Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>
		
			
				
	
	
		
			152 lines
		
	
	
		
			4.0 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			152 lines
		
	
	
		
			4.0 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
| import {
 | |
|   AudioCodec,
 | |
|   SystemConfig,
 | |
|   SystemConfigEntity,
 | |
|   SystemConfigKey,
 | |
|   SystemConfigValue,
 | |
|   ToneMapping,
 | |
|   TranscodeHWAccel,
 | |
|   TranscodePolicy,
 | |
|   VideoCodec,
 | |
| } from '@app/infra/entities';
 | |
| import { BadRequestException, Injectable, Logger } from '@nestjs/common';
 | |
| import * as _ from 'lodash';
 | |
| import { Subject } from 'rxjs';
 | |
| import { DeepPartial } from 'typeorm';
 | |
| import { QueueName } from '../job/job.constants';
 | |
| import { ISystemConfigRepository } from './system-config.repository';
 | |
| 
 | |
| export type SystemConfigValidator = (config: SystemConfig) => void | Promise<void>;
 | |
| 
 | |
| export const defaults = Object.freeze<SystemConfig>({
 | |
|   ffmpeg: {
 | |
|     crf: 23,
 | |
|     threads: 0,
 | |
|     preset: 'ultrafast',
 | |
|     targetVideoCodec: VideoCodec.H264,
 | |
|     targetAudioCodec: AudioCodec.AAC,
 | |
|     targetResolution: '720',
 | |
|     maxBitrate: '0',
 | |
|     twoPass: false,
 | |
|     transcode: TranscodePolicy.REQUIRED,
 | |
|     tonemap: ToneMapping.HABLE,
 | |
|     accel: TranscodeHWAccel.DISABLED,
 | |
|   },
 | |
|   job: {
 | |
|     [QueueName.BACKGROUND_TASK]: { concurrency: 5 },
 | |
|     [QueueName.CLIP_ENCODING]: { concurrency: 2 },
 | |
|     [QueueName.METADATA_EXTRACTION]: { concurrency: 5 },
 | |
|     [QueueName.OBJECT_TAGGING]: { concurrency: 2 },
 | |
|     [QueueName.RECOGNIZE_FACES]: { concurrency: 2 },
 | |
|     [QueueName.SEARCH]: { concurrency: 5 },
 | |
|     [QueueName.SIDECAR]: { concurrency: 5 },
 | |
|     [QueueName.STORAGE_TEMPLATE_MIGRATION]: { concurrency: 5 },
 | |
|     [QueueName.THUMBNAIL_GENERATION]: { concurrency: 5 },
 | |
|     [QueueName.VIDEO_CONVERSION]: { concurrency: 1 },
 | |
|   },
 | |
|   oauth: {
 | |
|     enabled: false,
 | |
|     issuerUrl: '',
 | |
|     clientId: '',
 | |
|     clientSecret: '',
 | |
|     mobileOverrideEnabled: false,
 | |
|     mobileRedirectUri: '',
 | |
|     scope: 'openid email profile',
 | |
|     storageLabelClaim: 'preferred_username',
 | |
|     buttonText: 'Login with OAuth',
 | |
|     autoRegister: true,
 | |
|     autoLaunch: false,
 | |
|   },
 | |
|   passwordLogin: {
 | |
|     enabled: true,
 | |
|   },
 | |
| 
 | |
|   storageTemplate: {
 | |
|     template: '{{y}}/{{y}}-{{MM}}-{{dd}}/{{filename}}',
 | |
|   },
 | |
| 
 | |
|   thumbnail: {
 | |
|     webpSize: 250,
 | |
|     jpegSize: 1440,
 | |
|   },
 | |
| });
 | |
| 
 | |
| const singleton = new Subject<SystemConfig>();
 | |
| 
 | |
| @Injectable()
 | |
| export class SystemConfigCore {
 | |
|   private logger = new Logger(SystemConfigCore.name);
 | |
|   private validators: SystemConfigValidator[] = [];
 | |
| 
 | |
|   public config$ = singleton;
 | |
| 
 | |
|   constructor(private repository: ISystemConfigRepository) {}
 | |
| 
 | |
|   public getDefaults(): SystemConfig {
 | |
|     return defaults;
 | |
|   }
 | |
| 
 | |
|   public addValidator(validator: SystemConfigValidator) {
 | |
|     this.validators.push(validator);
 | |
|   }
 | |
| 
 | |
|   public async getConfig() {
 | |
|     const overrides = await this.repository.load();
 | |
|     const config: DeepPartial<SystemConfig> = {};
 | |
|     for (const { key, value } of overrides) {
 | |
|       // set via dot notation
 | |
|       _.set(config, key, value);
 | |
|     }
 | |
| 
 | |
|     return _.defaultsDeep(config, defaults) as SystemConfig;
 | |
|   }
 | |
| 
 | |
|   public async updateConfig(config: SystemConfig): Promise<SystemConfig> {
 | |
|     try {
 | |
|       for (const validator of this.validators) {
 | |
|         await validator(config);
 | |
|       }
 | |
|     } catch (e) {
 | |
|       this.logger.warn(`Unable to save system config due to a validation error: ${e}`);
 | |
|       throw new BadRequestException(e instanceof Error ? e.message : e);
 | |
|     }
 | |
| 
 | |
|     const updates: SystemConfigEntity[] = [];
 | |
|     const deletes: SystemConfigEntity[] = [];
 | |
| 
 | |
|     for (const key of Object.values(SystemConfigKey)) {
 | |
|       // get via dot notation
 | |
|       const item = { key, value: _.get(config, key) as SystemConfigValue };
 | |
|       const defaultValue = _.get(defaults, key);
 | |
|       const isMissing = !_.has(config, key);
 | |
| 
 | |
|       if (isMissing || item.value === null || item.value === '' || item.value === defaultValue) {
 | |
|         deletes.push(item);
 | |
|         continue;
 | |
|       }
 | |
| 
 | |
|       updates.push(item);
 | |
|     }
 | |
| 
 | |
|     if (updates.length > 0) {
 | |
|       await this.repository.saveAll(updates);
 | |
|     }
 | |
| 
 | |
|     if (deletes.length > 0) {
 | |
|       await this.repository.deleteKeys(deletes.map((item) => item.key));
 | |
|     }
 | |
| 
 | |
|     const newConfig = await this.getConfig();
 | |
| 
 | |
|     this.config$.next(newConfig);
 | |
| 
 | |
|     return newConfig;
 | |
|   }
 | |
| 
 | |
|   public async refreshConfig() {
 | |
|     const newConfig = await this.getConfig();
 | |
| 
 | |
|     this.config$.next(newConfig);
 | |
|   }
 | |
| }
 |