mirror of
				https://github.com/KevinMidboe/immich.git
				synced 2025-10-29 17:40:28 +00:00 
			
		
		
		
	feat(web/server): webp thumbnail size configurable (#3598)
* 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>
This commit is contained in:
		
							
								
								
									
										25
									
								
								cli/src/api/open-api/api.ts
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										25
									
								
								cli/src/api/open-api/api.ts
									
									
									
										generated
									
									
									
								
							| @@ -2425,6 +2425,12 @@ export interface SystemConfigDto { | |||||||
|      * @memberof SystemConfigDto |      * @memberof SystemConfigDto | ||||||
|      */ |      */ | ||||||
|     'storageTemplate': SystemConfigStorageTemplateDto; |     'storageTemplate': SystemConfigStorageTemplateDto; | ||||||
|  |     /** | ||||||
|  |      *  | ||||||
|  |      * @type {SystemConfigThumbnailDto} | ||||||
|  |      * @memberof SystemConfigDto | ||||||
|  |      */ | ||||||
|  |     'thumbnail': SystemConfigThumbnailDto; | ||||||
| } | } | ||||||
| /** | /** | ||||||
|  *  |  *  | ||||||
| @@ -2716,6 +2722,25 @@ export interface SystemConfigTemplateStorageOptionDto { | |||||||
|      */ |      */ | ||||||
|     'yearOptions': Array<string>; |     'yearOptions': Array<string>; | ||||||
| } | } | ||||||
|  | /** | ||||||
|  |  *  | ||||||
|  |  * @export | ||||||
|  |  * @interface SystemConfigThumbnailDto | ||||||
|  |  */ | ||||||
|  | export interface SystemConfigThumbnailDto { | ||||||
|  |     /** | ||||||
|  |      *  | ||||||
|  |      * @type {number} | ||||||
|  |      * @memberof SystemConfigThumbnailDto | ||||||
|  |      */ | ||||||
|  |     'jpegSize': number; | ||||||
|  |     /** | ||||||
|  |      *  | ||||||
|  |      * @type {number} | ||||||
|  |      * @memberof SystemConfigThumbnailDto | ||||||
|  |      */ | ||||||
|  |     'webpSize': number; | ||||||
|  | } | ||||||
| /** | /** | ||||||
|  *  |  *  | ||||||
|  * @export |  * @export | ||||||
|   | |||||||
							
								
								
									
										3
									
								
								mobile/openapi/.openapi-generator/FILES
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										3
									
								
								mobile/openapi/.openapi-generator/FILES
									
									
									
										generated
									
									
									
								
							| @@ -104,6 +104,7 @@ doc/SystemConfigOAuthDto.md | |||||||
| doc/SystemConfigPasswordLoginDto.md | doc/SystemConfigPasswordLoginDto.md | ||||||
| doc/SystemConfigStorageTemplateDto.md | doc/SystemConfigStorageTemplateDto.md | ||||||
| doc/SystemConfigTemplateStorageOptionDto.md | doc/SystemConfigTemplateStorageOptionDto.md | ||||||
|  | doc/SystemConfigThumbnailDto.md | ||||||
| doc/TagApi.md | doc/TagApi.md | ||||||
| doc/TagResponseDto.md | doc/TagResponseDto.md | ||||||
| doc/TagTypeEnum.md | doc/TagTypeEnum.md | ||||||
| @@ -236,6 +237,7 @@ lib/model/system_config_o_auth_dto.dart | |||||||
| lib/model/system_config_password_login_dto.dart | lib/model/system_config_password_login_dto.dart | ||||||
| lib/model/system_config_storage_template_dto.dart | lib/model/system_config_storage_template_dto.dart | ||||||
| lib/model/system_config_template_storage_option_dto.dart | lib/model/system_config_template_storage_option_dto.dart | ||||||
|  | lib/model/system_config_thumbnail_dto.dart | ||||||
| lib/model/tag_response_dto.dart | lib/model/tag_response_dto.dart | ||||||
| lib/model/tag_type_enum.dart | lib/model/tag_type_enum.dart | ||||||
| lib/model/thumbnail_format.dart | lib/model/thumbnail_format.dart | ||||||
| @@ -355,6 +357,7 @@ test/system_config_o_auth_dto_test.dart | |||||||
| test/system_config_password_login_dto_test.dart | test/system_config_password_login_dto_test.dart | ||||||
| test/system_config_storage_template_dto_test.dart | test/system_config_storage_template_dto_test.dart | ||||||
| test/system_config_template_storage_option_dto_test.dart | test/system_config_template_storage_option_dto_test.dart | ||||||
|  | test/system_config_thumbnail_dto_test.dart | ||||||
| test/tag_api_test.dart | test/tag_api_test.dart | ||||||
| test/tag_response_dto_test.dart | test/tag_response_dto_test.dart | ||||||
| test/tag_type_enum_test.dart | test/tag_type_enum_test.dart | ||||||
|   | |||||||
							
								
								
									
										1
									
								
								mobile/openapi/README.md
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										1
									
								
								mobile/openapi/README.md
									
									
									
										generated
									
									
									
								
							| @@ -267,6 +267,7 @@ Class | Method | HTTP request | Description | |||||||
|  - [SystemConfigPasswordLoginDto](doc//SystemConfigPasswordLoginDto.md) |  - [SystemConfigPasswordLoginDto](doc//SystemConfigPasswordLoginDto.md) | ||||||
|  - [SystemConfigStorageTemplateDto](doc//SystemConfigStorageTemplateDto.md) |  - [SystemConfigStorageTemplateDto](doc//SystemConfigStorageTemplateDto.md) | ||||||
|  - [SystemConfigTemplateStorageOptionDto](doc//SystemConfigTemplateStorageOptionDto.md) |  - [SystemConfigTemplateStorageOptionDto](doc//SystemConfigTemplateStorageOptionDto.md) | ||||||
|  |  - [SystemConfigThumbnailDto](doc//SystemConfigThumbnailDto.md) | ||||||
|  - [TagResponseDto](doc//TagResponseDto.md) |  - [TagResponseDto](doc//TagResponseDto.md) | ||||||
|  - [TagTypeEnum](doc//TagTypeEnum.md) |  - [TagTypeEnum](doc//TagTypeEnum.md) | ||||||
|  - [ThumbnailFormat](doc//ThumbnailFormat.md) |  - [ThumbnailFormat](doc//ThumbnailFormat.md) | ||||||
|   | |||||||
							
								
								
									
										1
									
								
								mobile/openapi/doc/SystemConfigDto.md
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										1
									
								
								mobile/openapi/doc/SystemConfigDto.md
									
									
									
										generated
									
									
									
								
							| @@ -13,6 +13,7 @@ Name | Type | Description | Notes | |||||||
| **oauth** | [**SystemConfigOAuthDto**](SystemConfigOAuthDto.md) |  |  | **oauth** | [**SystemConfigOAuthDto**](SystemConfigOAuthDto.md) |  |  | ||||||
| **passwordLogin** | [**SystemConfigPasswordLoginDto**](SystemConfigPasswordLoginDto.md) |  |  | **passwordLogin** | [**SystemConfigPasswordLoginDto**](SystemConfigPasswordLoginDto.md) |  |  | ||||||
| **storageTemplate** | [**SystemConfigStorageTemplateDto**](SystemConfigStorageTemplateDto.md) |  |  | **storageTemplate** | [**SystemConfigStorageTemplateDto**](SystemConfigStorageTemplateDto.md) |  |  | ||||||
|  | **thumbnail** | [**SystemConfigThumbnailDto**](SystemConfigThumbnailDto.md) |  |  | ||||||
| 
 | 
 | ||||||
| [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) | [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) | ||||||
| 
 | 
 | ||||||
|   | |||||||
							
								
								
									
										16
									
								
								mobile/openapi/doc/SystemConfigThumbnailDto.md
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								mobile/openapi/doc/SystemConfigThumbnailDto.md
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | |||||||
|  | # openapi.model.SystemConfigThumbnailDto | ||||||
|  | 
 | ||||||
|  | ## Load the model package | ||||||
|  | ```dart | ||||||
|  | import 'package:openapi/api.dart'; | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | ## Properties | ||||||
|  | Name | Type | Description | Notes | ||||||
|  | ------------ | ------------- | ------------- | ------------- | ||||||
|  | **jpegSize** | **int** |  |  | ||||||
|  | **webpSize** | **int** |  |  | ||||||
|  | 
 | ||||||
|  | [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
							
								
								
									
										1
									
								
								mobile/openapi/lib/api.dart
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										1
									
								
								mobile/openapi/lib/api.dart
									
									
									
										generated
									
									
									
								
							| @@ -132,6 +132,7 @@ part 'model/system_config_o_auth_dto.dart'; | |||||||
| part 'model/system_config_password_login_dto.dart'; | part 'model/system_config_password_login_dto.dart'; | ||||||
| part 'model/system_config_storage_template_dto.dart'; | part 'model/system_config_storage_template_dto.dart'; | ||||||
| part 'model/system_config_template_storage_option_dto.dart'; | part 'model/system_config_template_storage_option_dto.dart'; | ||||||
|  | part 'model/system_config_thumbnail_dto.dart'; | ||||||
| part 'model/tag_response_dto.dart'; | part 'model/tag_response_dto.dart'; | ||||||
| part 'model/tag_type_enum.dart'; | part 'model/tag_type_enum.dart'; | ||||||
| part 'model/thumbnail_format.dart'; | part 'model/thumbnail_format.dart'; | ||||||
|   | |||||||
							
								
								
									
										2
									
								
								mobile/openapi/lib/api_client.dart
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										2
									
								
								mobile/openapi/lib/api_client.dart
									
									
									
										generated
									
									
									
								
							| @@ -359,6 +359,8 @@ class ApiClient { | |||||||
|           return SystemConfigStorageTemplateDto.fromJson(value); |           return SystemConfigStorageTemplateDto.fromJson(value); | ||||||
|         case 'SystemConfigTemplateStorageOptionDto': |         case 'SystemConfigTemplateStorageOptionDto': | ||||||
|           return SystemConfigTemplateStorageOptionDto.fromJson(value); |           return SystemConfigTemplateStorageOptionDto.fromJson(value); | ||||||
|  |         case 'SystemConfigThumbnailDto': | ||||||
|  |           return SystemConfigThumbnailDto.fromJson(value); | ||||||
|         case 'TagResponseDto': |         case 'TagResponseDto': | ||||||
|           return TagResponseDto.fromJson(value); |           return TagResponseDto.fromJson(value); | ||||||
|         case 'TagTypeEnum': |         case 'TagTypeEnum': | ||||||
|   | |||||||
							
								
								
									
										14
									
								
								mobile/openapi/lib/model/system_config_dto.dart
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										14
									
								
								mobile/openapi/lib/model/system_config_dto.dart
									
									
									
										generated
									
									
									
								
							| @@ -18,6 +18,7 @@ class SystemConfigDto { | |||||||
|     required this.oauth, |     required this.oauth, | ||||||
|     required this.passwordLogin, |     required this.passwordLogin, | ||||||
|     required this.storageTemplate, |     required this.storageTemplate, | ||||||
|  |     required this.thumbnail, | ||||||
|   }); |   }); | ||||||
| 
 | 
 | ||||||
|   SystemConfigFFmpegDto ffmpeg; |   SystemConfigFFmpegDto ffmpeg; | ||||||
| @@ -30,13 +31,16 @@ class SystemConfigDto { | |||||||
| 
 | 
 | ||||||
|   SystemConfigStorageTemplateDto storageTemplate; |   SystemConfigStorageTemplateDto storageTemplate; | ||||||
| 
 | 
 | ||||||
|  |   SystemConfigThumbnailDto thumbnail; | ||||||
|  | 
 | ||||||
|   @override |   @override | ||||||
|   bool operator ==(Object other) => identical(this, other) || other is SystemConfigDto && |   bool operator ==(Object other) => identical(this, other) || other is SystemConfigDto && | ||||||
|      other.ffmpeg == ffmpeg && |      other.ffmpeg == ffmpeg && | ||||||
|      other.job == job && |      other.job == job && | ||||||
|      other.oauth == oauth && |      other.oauth == oauth && | ||||||
|      other.passwordLogin == passwordLogin && |      other.passwordLogin == passwordLogin && | ||||||
|      other.storageTemplate == storageTemplate; |      other.storageTemplate == storageTemplate && | ||||||
|  |      other.thumbnail == thumbnail; | ||||||
| 
 | 
 | ||||||
|   @override |   @override | ||||||
|   int get hashCode => |   int get hashCode => | ||||||
| @@ -45,10 +49,11 @@ class SystemConfigDto { | |||||||
|     (job.hashCode) + |     (job.hashCode) + | ||||||
|     (oauth.hashCode) + |     (oauth.hashCode) + | ||||||
|     (passwordLogin.hashCode) + |     (passwordLogin.hashCode) + | ||||||
|     (storageTemplate.hashCode); |     (storageTemplate.hashCode) + | ||||||
|  |     (thumbnail.hashCode); | ||||||
| 
 | 
 | ||||||
|   @override |   @override | ||||||
|   String toString() => 'SystemConfigDto[ffmpeg=$ffmpeg, job=$job, oauth=$oauth, passwordLogin=$passwordLogin, storageTemplate=$storageTemplate]'; |   String toString() => 'SystemConfigDto[ffmpeg=$ffmpeg, job=$job, oauth=$oauth, passwordLogin=$passwordLogin, storageTemplate=$storageTemplate, thumbnail=$thumbnail]'; | ||||||
| 
 | 
 | ||||||
|   Map<String, dynamic> toJson() { |   Map<String, dynamic> toJson() { | ||||||
|     final json = <String, dynamic>{}; |     final json = <String, dynamic>{}; | ||||||
| @@ -57,6 +62,7 @@ class SystemConfigDto { | |||||||
|       json[r'oauth'] = this.oauth; |       json[r'oauth'] = this.oauth; | ||||||
|       json[r'passwordLogin'] = this.passwordLogin; |       json[r'passwordLogin'] = this.passwordLogin; | ||||||
|       json[r'storageTemplate'] = this.storageTemplate; |       json[r'storageTemplate'] = this.storageTemplate; | ||||||
|  |       json[r'thumbnail'] = this.thumbnail; | ||||||
|     return json; |     return json; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
| @@ -73,6 +79,7 @@ class SystemConfigDto { | |||||||
|         oauth: SystemConfigOAuthDto.fromJson(json[r'oauth'])!, |         oauth: SystemConfigOAuthDto.fromJson(json[r'oauth'])!, | ||||||
|         passwordLogin: SystemConfigPasswordLoginDto.fromJson(json[r'passwordLogin'])!, |         passwordLogin: SystemConfigPasswordLoginDto.fromJson(json[r'passwordLogin'])!, | ||||||
|         storageTemplate: SystemConfigStorageTemplateDto.fromJson(json[r'storageTemplate'])!, |         storageTemplate: SystemConfigStorageTemplateDto.fromJson(json[r'storageTemplate'])!, | ||||||
|  |         thumbnail: SystemConfigThumbnailDto.fromJson(json[r'thumbnail'])!, | ||||||
|       ); |       ); | ||||||
|     } |     } | ||||||
|     return null; |     return null; | ||||||
| @@ -125,6 +132,7 @@ class SystemConfigDto { | |||||||
|     'oauth', |     'oauth', | ||||||
|     'passwordLogin', |     'passwordLogin', | ||||||
|     'storageTemplate', |     'storageTemplate', | ||||||
|  |     'thumbnail', | ||||||
|   }; |   }; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|   | |||||||
							
								
								
									
										106
									
								
								mobile/openapi/lib/model/system_config_thumbnail_dto.dart
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										106
									
								
								mobile/openapi/lib/model/system_config_thumbnail_dto.dart
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,106 @@ | |||||||
|  | // | ||||||
|  | // AUTO-GENERATED FILE, DO NOT MODIFY! | ||||||
|  | // | ||||||
|  | // @dart=2.12 | ||||||
|  | 
 | ||||||
|  | // ignore_for_file: unused_element, unused_import | ||||||
|  | // ignore_for_file: always_put_required_named_parameters_first | ||||||
|  | // ignore_for_file: constant_identifier_names | ||||||
|  | // ignore_for_file: lines_longer_than_80_chars | ||||||
|  | 
 | ||||||
|  | part of openapi.api; | ||||||
|  | 
 | ||||||
|  | class SystemConfigThumbnailDto { | ||||||
|  |   /// Returns a new [SystemConfigThumbnailDto] instance. | ||||||
|  |   SystemConfigThumbnailDto({ | ||||||
|  |     required this.jpegSize, | ||||||
|  |     required this.webpSize, | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   int jpegSize; | ||||||
|  | 
 | ||||||
|  |   int webpSize; | ||||||
|  | 
 | ||||||
|  |   @override | ||||||
|  |   bool operator ==(Object other) => identical(this, other) || other is SystemConfigThumbnailDto && | ||||||
|  |      other.jpegSize == jpegSize && | ||||||
|  |      other.webpSize == webpSize; | ||||||
|  | 
 | ||||||
|  |   @override | ||||||
|  |   int get hashCode => | ||||||
|  |     // ignore: unnecessary_parenthesis | ||||||
|  |     (jpegSize.hashCode) + | ||||||
|  |     (webpSize.hashCode); | ||||||
|  | 
 | ||||||
|  |   @override | ||||||
|  |   String toString() => 'SystemConfigThumbnailDto[jpegSize=$jpegSize, webpSize=$webpSize]'; | ||||||
|  | 
 | ||||||
|  |   Map<String, dynamic> toJson() { | ||||||
|  |     final json = <String, dynamic>{}; | ||||||
|  |       json[r'jpegSize'] = this.jpegSize; | ||||||
|  |       json[r'webpSize'] = this.webpSize; | ||||||
|  |     return json; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /// Returns a new [SystemConfigThumbnailDto] instance and imports its values from | ||||||
|  |   /// [value] if it's a [Map], null otherwise. | ||||||
|  |   // ignore: prefer_constructors_over_static_methods | ||||||
|  |   static SystemConfigThumbnailDto? fromJson(dynamic value) { | ||||||
|  |     if (value is Map) { | ||||||
|  |       final json = value.cast<String, dynamic>(); | ||||||
|  | 
 | ||||||
|  |       return SystemConfigThumbnailDto( | ||||||
|  |         jpegSize: mapValueOfType<int>(json, r'jpegSize')!, | ||||||
|  |         webpSize: mapValueOfType<int>(json, r'webpSize')!, | ||||||
|  |       ); | ||||||
|  |     } | ||||||
|  |     return null; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static List<SystemConfigThumbnailDto> listFromJson(dynamic json, {bool growable = false,}) { | ||||||
|  |     final result = <SystemConfigThumbnailDto>[]; | ||||||
|  |     if (json is List && json.isNotEmpty) { | ||||||
|  |       for (final row in json) { | ||||||
|  |         final value = SystemConfigThumbnailDto.fromJson(row); | ||||||
|  |         if (value != null) { | ||||||
|  |           result.add(value); | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     return result.toList(growable: growable); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static Map<String, SystemConfigThumbnailDto> mapFromJson(dynamic json) { | ||||||
|  |     final map = <String, SystemConfigThumbnailDto>{}; | ||||||
|  |     if (json is Map && json.isNotEmpty) { | ||||||
|  |       json = json.cast<String, dynamic>(); // ignore: parameter_assignments | ||||||
|  |       for (final entry in json.entries) { | ||||||
|  |         final value = SystemConfigThumbnailDto.fromJson(entry.value); | ||||||
|  |         if (value != null) { | ||||||
|  |           map[entry.key] = value; | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     return map; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   // maps a json object with a list of SystemConfigThumbnailDto-objects as value to a dart map | ||||||
|  |   static Map<String, List<SystemConfigThumbnailDto>> mapListFromJson(dynamic json, {bool growable = false,}) { | ||||||
|  |     final map = <String, List<SystemConfigThumbnailDto>>{}; | ||||||
|  |     if (json is Map && json.isNotEmpty) { | ||||||
|  |       // ignore: parameter_assignments | ||||||
|  |       json = json.cast<String, dynamic>(); | ||||||
|  |       for (final entry in json.entries) { | ||||||
|  |         map[entry.key] = SystemConfigThumbnailDto.listFromJson(entry.value, growable: growable,); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     return map; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /// The list of required keys that must be present in a JSON. | ||||||
|  |   static const requiredKeys = <String>{ | ||||||
|  |     'jpegSize', | ||||||
|  |     'webpSize', | ||||||
|  |   }; | ||||||
|  | } | ||||||
|  | 
 | ||||||
							
								
								
									
										5
									
								
								mobile/openapi/test/system_config_dto_test.dart
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										5
									
								
								mobile/openapi/test/system_config_dto_test.dart
									
									
									
										generated
									
									
									
								
							| @@ -41,6 +41,11 @@ void main() { | |||||||
|       // TODO |       // TODO | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|  |     // SystemConfigThumbnailDto thumbnail | ||||||
|  |     test('to test the property `thumbnail`', () async { | ||||||
|  |       // TODO | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
|   }); |   }); | ||||||
| 
 | 
 | ||||||
|   | |||||||
							
								
								
									
										32
									
								
								mobile/openapi/test/system_config_thumbnail_dto_test.dart
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								mobile/openapi/test/system_config_thumbnail_dto_test.dart
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,32 @@ | |||||||
|  | // | ||||||
|  | // AUTO-GENERATED FILE, DO NOT MODIFY! | ||||||
|  | // | ||||||
|  | // @dart=2.12 | ||||||
|  | 
 | ||||||
|  | // ignore_for_file: unused_element, unused_import | ||||||
|  | // ignore_for_file: always_put_required_named_parameters_first | ||||||
|  | // ignore_for_file: constant_identifier_names | ||||||
|  | // ignore_for_file: lines_longer_than_80_chars | ||||||
|  | 
 | ||||||
|  | import 'package:openapi/api.dart'; | ||||||
|  | import 'package:test/test.dart'; | ||||||
|  | 
 | ||||||
|  | // tests for SystemConfigThumbnailDto | ||||||
|  | void main() { | ||||||
|  |   // final instance = SystemConfigThumbnailDto(); | ||||||
|  | 
 | ||||||
|  |   group('test SystemConfigThumbnailDto', () { | ||||||
|  |     // int jpegSize | ||||||
|  |     test('to test the property `jpegSize`', () async { | ||||||
|  |       // TODO | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     // int webpSize | ||||||
|  |     test('to test the property `webpSize`', () async { | ||||||
|  |       // TODO | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  | } | ||||||
| @@ -6590,6 +6590,9 @@ | |||||||
|           }, |           }, | ||||||
|           "storageTemplate": { |           "storageTemplate": { | ||||||
|             "$ref": "#/components/schemas/SystemConfigStorageTemplateDto" |             "$ref": "#/components/schemas/SystemConfigStorageTemplateDto" | ||||||
|  |           }, | ||||||
|  |           "thumbnail": { | ||||||
|  |             "$ref": "#/components/schemas/SystemConfigThumbnailDto" | ||||||
|           } |           } | ||||||
|         }, |         }, | ||||||
|         "required": [ |         "required": [ | ||||||
| @@ -6597,7 +6600,8 @@ | |||||||
|           "oauth", |           "oauth", | ||||||
|           "passwordLogin", |           "passwordLogin", | ||||||
|           "storageTemplate", |           "storageTemplate", | ||||||
|           "job" |           "job", | ||||||
|  |           "thumbnail" | ||||||
|         ], |         ], | ||||||
|         "type": "object" |         "type": "object" | ||||||
|       }, |       }, | ||||||
| @@ -6828,6 +6832,21 @@ | |||||||
|         ], |         ], | ||||||
|         "type": "object" |         "type": "object" | ||||||
|       }, |       }, | ||||||
|  |       "SystemConfigThumbnailDto": { | ||||||
|  |         "properties": { | ||||||
|  |           "jpegSize": { | ||||||
|  |             "type": "integer" | ||||||
|  |           }, | ||||||
|  |           "webpSize": { | ||||||
|  |             "type": "integer" | ||||||
|  |           } | ||||||
|  |         }, | ||||||
|  |         "required": [ | ||||||
|  |           "webpSize", | ||||||
|  |           "jpegSize" | ||||||
|  |         ], | ||||||
|  |         "type": "object" | ||||||
|  |       }, | ||||||
|       "TagResponseDto": { |       "TagResponseDto": { | ||||||
|         "properties": { |         "properties": { | ||||||
|           "id": { |           "id": { | ||||||
|   | |||||||
| @@ -1,3 +1 @@ | |||||||
| export const JPEG_THUMBNAIL_SIZE = 1440; |  | ||||||
| export const WEBP_THUMBNAIL_SIZE = 250; |  | ||||||
| export const FACE_THUMBNAIL_SIZE = 250; | export const FACE_THUMBNAIL_SIZE = 250; | ||||||
|   | |||||||
| @@ -7,7 +7,6 @@ import { IBaseJob, IEntityJob, IJobRepository, JobName, JOBS_ASSET_PAGINATION_SI | |||||||
| import { IStorageRepository, StorageCore, StorageFolder } from '../storage'; | import { IStorageRepository, StorageCore, StorageFolder } from '../storage'; | ||||||
| import { ISystemConfigRepository, SystemConfigFFmpegDto } from '../system-config'; | import { ISystemConfigRepository, SystemConfigFFmpegDto } from '../system-config'; | ||||||
| import { SystemConfigCore } from '../system-config/system-config.core'; | import { SystemConfigCore } from '../system-config/system-config.core'; | ||||||
| import { JPEG_THUMBNAIL_SIZE, WEBP_THUMBNAIL_SIZE } from './media.constant'; |  | ||||||
| import { AudioStreamInfo, IMediaRepository, VideoCodecHWConfig, VideoStreamInfo } from './media.repository'; | import { AudioStreamInfo, IMediaRepository, VideoCodecHWConfig, VideoStreamInfo } from './media.repository'; | ||||||
| import { H264Config, HEVCConfig, NVENCConfig, QSVConfig, ThumbnailConfig, VAAPIConfig, VP9Config } from './media.util'; | import { H264Config, HEVCConfig, NVENCConfig, QSVConfig, ThumbnailConfig, VAAPIConfig, VP9Config } from './media.util'; | ||||||
|  |  | ||||||
| @@ -63,11 +62,12 @@ export class MediaService { | |||||||
|     const resizePath = this.storageCore.getFolderLocation(StorageFolder.THUMBNAILS, asset.ownerId); |     const resizePath = this.storageCore.getFolderLocation(StorageFolder.THUMBNAILS, asset.ownerId); | ||||||
|     this.storageRepository.mkdirSync(resizePath); |     this.storageRepository.mkdirSync(resizePath); | ||||||
|     const jpegThumbnailPath = join(resizePath, `${asset.id}.jpeg`); |     const jpegThumbnailPath = join(resizePath, `${asset.id}.jpeg`); | ||||||
|  |     const { thumbnail } = await this.configCore.getConfig(); | ||||||
|  |  | ||||||
|     switch (asset.type) { |     switch (asset.type) { | ||||||
|       case AssetType.IMAGE: |       case AssetType.IMAGE: | ||||||
|         await this.mediaRepository.resize(asset.originalPath, jpegThumbnailPath, { |         await this.mediaRepository.resize(asset.originalPath, jpegThumbnailPath, { | ||||||
|           size: JPEG_THUMBNAIL_SIZE, |           size: thumbnail.jpegSize, | ||||||
|           format: 'jpeg', |           format: 'jpeg', | ||||||
|         }); |         }); | ||||||
|         this.logger.log(`Successfully generated image thumbnail ${asset.id}`); |         this.logger.log(`Successfully generated image thumbnail ${asset.id}`); | ||||||
| @@ -80,7 +80,7 @@ export class MediaService { | |||||||
|           return false; |           return false; | ||||||
|         } |         } | ||||||
|         const { ffmpeg } = await this.configCore.getConfig(); |         const { ffmpeg } = await this.configCore.getConfig(); | ||||||
|         const config = { ...ffmpeg, targetResolution: JPEG_THUMBNAIL_SIZE.toString(), twoPass: false }; |         const config = { ...ffmpeg, targetResolution: thumbnail.jpegSize.toString(), twoPass: false }; | ||||||
|         const options = new ThumbnailConfig(config).getOptions(mainVideoStream); |         const options = new ThumbnailConfig(config).getOptions(mainVideoStream); | ||||||
|         await this.mediaRepository.transcode(asset.originalPath, jpegThumbnailPath, options); |         await this.mediaRepository.transcode(asset.originalPath, jpegThumbnailPath, options); | ||||||
|         this.logger.log(`Successfully generated video thumbnail ${asset.id}`); |         this.logger.log(`Successfully generated video thumbnail ${asset.id}`); | ||||||
| @@ -100,7 +100,8 @@ export class MediaService { | |||||||
|  |  | ||||||
|     const webpPath = asset.resizePath.replace('jpeg', 'webp').replace('jpg', 'webp'); |     const webpPath = asset.resizePath.replace('jpeg', 'webp').replace('jpg', 'webp'); | ||||||
|  |  | ||||||
|     await this.mediaRepository.resize(asset.resizePath, webpPath, { size: WEBP_THUMBNAIL_SIZE, format: 'webp' }); |     const { thumbnail } = await this.configCore.getConfig(); | ||||||
|  |     await this.mediaRepository.resize(asset.resizePath, webpPath, { size: thumbnail.webpSize, format: 'webp' }); | ||||||
|     await this.assetRepository.save({ id: asset.id, webpPath }); |     await this.assetRepository.save({ id: asset.id, webpPath }); | ||||||
|  |  | ||||||
|     return true; |     return true; | ||||||
|   | |||||||
| @@ -2,4 +2,5 @@ export * from './system-config-ffmpeg.dto'; | |||||||
| export * from './system-config-oauth.dto'; | export * from './system-config-oauth.dto'; | ||||||
| export * from './system-config-password-login.dto'; | export * from './system-config-password-login.dto'; | ||||||
| export * from './system-config-storage-template.dto'; | export * from './system-config-storage-template.dto'; | ||||||
|  | export * from './system-config-thumbnail.dto'; | ||||||
| export * from './system-config.dto'; | export * from './system-config.dto'; | ||||||
|   | |||||||
| @@ -0,0 +1,15 @@ | |||||||
|  | import { ApiProperty } from '@nestjs/swagger'; | ||||||
|  | import { Type } from 'class-transformer'; | ||||||
|  | import { IsInt } from 'class-validator'; | ||||||
|  |  | ||||||
|  | export class SystemConfigThumbnailDto { | ||||||
|  |   @IsInt() | ||||||
|  |   @Type(() => Number) | ||||||
|  |   @ApiProperty({ type: 'integer' }) | ||||||
|  |   webpSize!: number; | ||||||
|  |  | ||||||
|  |   @IsInt() | ||||||
|  |   @Type(() => Number) | ||||||
|  |   @ApiProperty({ type: 'integer' }) | ||||||
|  |   jpegSize!: number; | ||||||
|  | } | ||||||
| @@ -1,3 +1,4 @@ | |||||||
|  | import { SystemConfigThumbnailDto } from '@app/domain/system-config'; | ||||||
| import { SystemConfig } from '@app/infra/entities'; | import { SystemConfig } from '@app/infra/entities'; | ||||||
| import { Type } from 'class-transformer'; | import { Type } from 'class-transformer'; | ||||||
| import { IsObject, ValidateNested } from 'class-validator'; | import { IsObject, ValidateNested } from 'class-validator'; | ||||||
| @@ -32,6 +33,11 @@ export class SystemConfigDto { | |||||||
|   @ValidateNested() |   @ValidateNested() | ||||||
|   @IsObject() |   @IsObject() | ||||||
|   job!: SystemConfigJobDto; |   job!: SystemConfigJobDto; | ||||||
|  |  | ||||||
|  |   @Type(() => SystemConfigThumbnailDto) | ||||||
|  |   @ValidateNested() | ||||||
|  |   @IsObject() | ||||||
|  |   thumbnail!: SystemConfigThumbnailDto; | ||||||
| } | } | ||||||
|  |  | ||||||
| export function mapConfig(config: SystemConfig): SystemConfigDto { | export function mapConfig(config: SystemConfig): SystemConfigDto { | ||||||
|   | |||||||
| @@ -64,6 +64,11 @@ export const defaults = Object.freeze<SystemConfig>({ | |||||||
|   storageTemplate: { |   storageTemplate: { | ||||||
|     template: '{{y}}/{{y}}-{{MM}}-{{dd}}/{{filename}}', |     template: '{{y}}/{{y}}-{{MM}}-{{dd}}/{{filename}}', | ||||||
|   }, |   }, | ||||||
|  |  | ||||||
|  |   thumbnail: { | ||||||
|  |     webpSize: 250, | ||||||
|  |     jpegSize: 1440, | ||||||
|  |   }, | ||||||
| }); | }); | ||||||
|  |  | ||||||
| const singleton = new Subject<SystemConfig>(); | const singleton = new Subject<SystemConfig>(); | ||||||
|   | |||||||
| @@ -65,6 +65,10 @@ const updatedConfig = Object.freeze<SystemConfig>({ | |||||||
|   storageTemplate: { |   storageTemplate: { | ||||||
|     template: '{{y}}/{{y}}-{{MM}}-{{dd}}/{{filename}}', |     template: '{{y}}/{{y}}-{{MM}}-{{dd}}/{{filename}}', | ||||||
|   }, |   }, | ||||||
|  |   thumbnail: { | ||||||
|  |     webpSize: 250, | ||||||
|  |     jpegSize: 1440, | ||||||
|  |   }, | ||||||
| }); | }); | ||||||
|  |  | ||||||
| describe(SystemConfigService.name, () => { | describe(SystemConfigService.name, () => { | ||||||
|   | |||||||
| @@ -52,6 +52,9 @@ export enum SystemConfigKey { | |||||||
|   PASSWORD_LOGIN_ENABLED = 'passwordLogin.enabled', |   PASSWORD_LOGIN_ENABLED = 'passwordLogin.enabled', | ||||||
|  |  | ||||||
|   STORAGE_TEMPLATE = 'storageTemplate.template', |   STORAGE_TEMPLATE = 'storageTemplate.template', | ||||||
|  |  | ||||||
|  |   THUMBNAIL_WEBP_SIZE = 'thumbnail.webpSize', | ||||||
|  |   THUMBNAIL_JPEG_SIZE = 'thumbnail.jpegSize', | ||||||
| } | } | ||||||
|  |  | ||||||
| export enum TranscodePolicy { | export enum TranscodePolicy { | ||||||
| @@ -121,4 +124,8 @@ export interface SystemConfig { | |||||||
|   storageTemplate: { |   storageTemplate: { | ||||||
|     template: string; |     template: string; | ||||||
|   }; |   }; | ||||||
|  |   thumbnail: { | ||||||
|  |     webpSize: number; | ||||||
|  |     jpegSize: number; | ||||||
|  |   }; | ||||||
| } | } | ||||||
|   | |||||||
| @@ -12,7 +12,7 @@ export class MediaRepository implements IMediaRepository { | |||||||
|   private logger = new Logger(MediaRepository.name); |   private logger = new Logger(MediaRepository.name); | ||||||
|  |  | ||||||
|   crop(input: string, options: CropOptions): Promise<Buffer> { |   crop(input: string, options: CropOptions): Promise<Buffer> { | ||||||
|     return sharp(input, { failOnError: false }) |     return sharp(input, { failOn: 'none' }) | ||||||
|       .extract({ |       .extract({ | ||||||
|         left: options.left, |         left: options.left, | ||||||
|         top: options.top, |         top: options.top, | ||||||
| @@ -23,7 +23,7 @@ export class MediaRepository implements IMediaRepository { | |||||||
|   } |   } | ||||||
|  |  | ||||||
|   async resize(input: string | Buffer, output: string, options: ResizeOptions): Promise<void> { |   async resize(input: string | Buffer, output: string, options: ResizeOptions): Promise<void> { | ||||||
|     await sharp(input, { failOnError: false }) |     await sharp(input, { failOn: 'none' }) | ||||||
|       .resize(options.size, options.size, { fit: 'outside', withoutEnlargement: true }) |       .resize(options.size, options.size, { fit: 'outside', withoutEnlargement: true }) | ||||||
|       .rotate() |       .rotate() | ||||||
|       .toFormat(options.format) |       .toFormat(options.format) | ||||||
|   | |||||||
							
								
								
									
										25
									
								
								web/src/api/open-api/api.ts
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										25
									
								
								web/src/api/open-api/api.ts
									
									
									
										generated
									
									
									
								
							| @@ -2425,6 +2425,12 @@ export interface SystemConfigDto { | |||||||
|      * @memberof SystemConfigDto |      * @memberof SystemConfigDto | ||||||
|      */ |      */ | ||||||
|     'storageTemplate': SystemConfigStorageTemplateDto; |     'storageTemplate': SystemConfigStorageTemplateDto; | ||||||
|  |     /** | ||||||
|  |      *  | ||||||
|  |      * @type {SystemConfigThumbnailDto} | ||||||
|  |      * @memberof SystemConfigDto | ||||||
|  |      */ | ||||||
|  |     'thumbnail': SystemConfigThumbnailDto; | ||||||
| } | } | ||||||
| /** | /** | ||||||
|  *  |  *  | ||||||
| @@ -2716,6 +2722,25 @@ export interface SystemConfigTemplateStorageOptionDto { | |||||||
|      */ |      */ | ||||||
|     'yearOptions': Array<string>; |     'yearOptions': Array<string>; | ||||||
| } | } | ||||||
|  | /** | ||||||
|  |  *  | ||||||
|  |  * @export | ||||||
|  |  * @interface SystemConfigThumbnailDto | ||||||
|  |  */ | ||||||
|  | export interface SystemConfigThumbnailDto { | ||||||
|  |     /** | ||||||
|  |      *  | ||||||
|  |      * @type {number} | ||||||
|  |      * @memberof SystemConfigThumbnailDto | ||||||
|  |      */ | ||||||
|  |     'jpegSize': number; | ||||||
|  |     /** | ||||||
|  |      *  | ||||||
|  |      * @type {number} | ||||||
|  |      * @memberof SystemConfigThumbnailDto | ||||||
|  |      */ | ||||||
|  |     'webpSize': number; | ||||||
|  | } | ||||||
| /** | /** | ||||||
|  *  |  *  | ||||||
|  * @export |  * @export | ||||||
|   | |||||||
| @@ -27,7 +27,7 @@ | |||||||
|   }; |   }; | ||||||
| </script> | </script> | ||||||
|  |  | ||||||
| <div class="w-full"> | <div class="mb-4 w-full"> | ||||||
|   <div class={`flex h-[26px] place-items-center gap-1`}> |   <div class={`flex h-[26px] place-items-center gap-1`}> | ||||||
|     <label class={`immich-form-label text-sm`} for={label}>{label}</label> |     <label class={`immich-form-label text-sm`} for={label}>{label}</label> | ||||||
|     {#if required} |     {#if required} | ||||||
| @@ -45,7 +45,7 @@ | |||||||
|   </div> |   </div> | ||||||
|  |  | ||||||
|   {#if desc} |   {#if desc} | ||||||
|     <p class="immich-form-label pb-2 text-xs" id="{label}-desc"> |     <p class="immich-form-label pb-2 text-sm" id="{label}-desc"> | ||||||
|       {desc} |       {desc} | ||||||
|     </p> |     </p> | ||||||
|   {/if} |   {/if} | ||||||
|   | |||||||
| @@ -2,19 +2,23 @@ | |||||||
|   import { quintOut } from 'svelte/easing'; |   import { quintOut } from 'svelte/easing'; | ||||||
|   import { fly } from 'svelte/transition'; |   import { fly } from 'svelte/transition'; | ||||||
|  |  | ||||||
|   export let value: string; |   export let value: string | number; | ||||||
|   export let options: { value: string; text: string }[]; |   export let options: { value: string | number; text: string }[]; | ||||||
|   export let label = ''; |   export let label = ''; | ||||||
|   export let desc = ''; |   export let desc = ''; | ||||||
|   export let name = ''; |   export let name = ''; | ||||||
|   export let isEdited = false; |   export let isEdited = false; | ||||||
|  |   export let number = false; | ||||||
|  |  | ||||||
|   const handleChange = (e: Event) => { |   const handleChange = (e: Event) => { | ||||||
|     value = (e.target as HTMLInputElement).value; |     value = (e.target as HTMLInputElement).value; | ||||||
|  |     if (number) { | ||||||
|  |       value = parseInt(value); | ||||||
|  |     } | ||||||
|   }; |   }; | ||||||
| </script> | </script> | ||||||
|  |  | ||||||
| <div class="w-full"> | <div class="mb-4 w-full"> | ||||||
|   <div class={`flex h-[26px] place-items-center gap-1`}> |   <div class={`flex h-[26px] place-items-center gap-1`}> | ||||||
|     <label class={`immich-form-label text-sm`} for="{name}-select">{label}</label> |     <label class={`immich-form-label text-sm`} for="{name}-select">{label}</label> | ||||||
|  |  | ||||||
| @@ -29,7 +33,7 @@ | |||||||
|   </div> |   </div> | ||||||
|  |  | ||||||
|   {#if desc} |   {#if desc} | ||||||
|     <p class="immich-form-label pb-2 text-xs" id="{name}-desc"> |     <p class="immich-form-label pb-2 text-sm" id="{name}-desc"> | ||||||
|       {desc} |       {desc} | ||||||
|     </p> |     </p> | ||||||
|   {/if} |   {/if} | ||||||
|   | |||||||
| @@ -0,0 +1,121 @@ | |||||||
|  | <script lang="ts"> | ||||||
|  |   import SettingSelect from '$lib/components/admin-page/settings/setting-select.svelte'; | ||||||
|  |   import { api, SystemConfigThumbnailDto } from '@api'; | ||||||
|  |   import { fade } from 'svelte/transition'; | ||||||
|  |   import { isEqual } from 'lodash-es'; | ||||||
|  |   import SettingButtonsRow from '$lib/components/admin-page/settings/setting-buttons-row.svelte'; | ||||||
|  |   import { | ||||||
|  |     notificationController, | ||||||
|  |     NotificationType, | ||||||
|  |   } from '$lib/components/shared-components/notification/notification'; | ||||||
|  |  | ||||||
|  |   export let thumbnailConfig: SystemConfigThumbnailDto; // this is the config that is being edited | ||||||
|  |  | ||||||
|  |   let savedConfig: SystemConfigThumbnailDto; | ||||||
|  |   let defaultConfig: SystemConfigThumbnailDto; | ||||||
|  |  | ||||||
|  |   async function getConfigs() { | ||||||
|  |     [savedConfig, defaultConfig] = await Promise.all([ | ||||||
|  |       api.systemConfigApi.getConfig().then((res) => res.data.thumbnail), | ||||||
|  |       api.systemConfigApi.getDefaults().then((res) => res.data.thumbnail), | ||||||
|  |     ]); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   async function reset() { | ||||||
|  |     const { data: resetConfig } = await api.systemConfigApi.getConfig(); | ||||||
|  |  | ||||||
|  |     thumbnailConfig = { ...resetConfig.thumbnail }; | ||||||
|  |     savedConfig = { ...resetConfig.thumbnail }; | ||||||
|  |  | ||||||
|  |     notificationController.show({ | ||||||
|  |       message: 'Reset thumbnail settings to the recent saved settings', | ||||||
|  |       type: NotificationType.Info, | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   async function resetToDefault() { | ||||||
|  |     const { data: configs } = await api.systemConfigApi.getDefaults(); | ||||||
|  |  | ||||||
|  |     thumbnailConfig = { ...configs.thumbnail }; | ||||||
|  |     defaultConfig = { ...configs.thumbnail }; | ||||||
|  |  | ||||||
|  |     notificationController.show({ | ||||||
|  |       message: 'Reset thumbnail settings to default', | ||||||
|  |       type: NotificationType.Info, | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   async function saveSetting() { | ||||||
|  |     try { | ||||||
|  |       const { data: configs } = await api.systemConfigApi.getConfig(); | ||||||
|  |  | ||||||
|  |       const result = await api.systemConfigApi.updateConfig({ | ||||||
|  |         systemConfigDto: { | ||||||
|  |           ...configs, | ||||||
|  |           thumbnail: thumbnailConfig, | ||||||
|  |         }, | ||||||
|  |       }); | ||||||
|  |  | ||||||
|  |       thumbnailConfig = { ...result.data.thumbnail }; | ||||||
|  |       savedConfig = { ...result.data.thumbnail }; | ||||||
|  |  | ||||||
|  |       notificationController.show({ | ||||||
|  |         message: 'Thumbnail settings saved', | ||||||
|  |         type: NotificationType.Info, | ||||||
|  |       }); | ||||||
|  |     } catch (e) { | ||||||
|  |       console.error('Error [thumbnail-settings] [saveSetting]', e); | ||||||
|  |       notificationController.show({ | ||||||
|  |         message: 'Unable to save settings', | ||||||
|  |         type: NotificationType.Error, | ||||||
|  |       }); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | </script> | ||||||
|  |  | ||||||
|  | <div> | ||||||
|  |   {#await getConfigs() then} | ||||||
|  |     <div in:fade={{ duration: 500 }}> | ||||||
|  |       <form autocomplete="off" on:submit|preventDefault> | ||||||
|  |         <div class="ml-4 mt-4 flex flex-col gap-4"> | ||||||
|  |           <SettingSelect | ||||||
|  |             label="WEBP RESOLUTION" | ||||||
|  |             desc="Higher resolutions can preserve more detail but take longer to encode, have larger file sizes, and can reduce app responsiveness." | ||||||
|  |             number | ||||||
|  |             bind:value={thumbnailConfig.webpSize} | ||||||
|  |             options={[ | ||||||
|  |               { value: 1080, text: '1080p' }, | ||||||
|  |               { value: 720, text: '720p' }, | ||||||
|  |               { value: 480, text: '480p' }, | ||||||
|  |               { value: 250, text: '250p' }, | ||||||
|  |             ]} | ||||||
|  |             name="resolution" | ||||||
|  |             isEdited={!(thumbnailConfig.webpSize === savedConfig.webpSize)} | ||||||
|  |           /> | ||||||
|  |  | ||||||
|  |           <SettingSelect | ||||||
|  |             label="JPEG RESOLUTION" | ||||||
|  |             desc="Higher resolutions can preserve more detail but take longer to encode, have larger file sizes, and can reduce app responsiveness." | ||||||
|  |             number | ||||||
|  |             bind:value={thumbnailConfig.jpegSize} | ||||||
|  |             options={[ | ||||||
|  |               { value: 2160, text: '4K' }, | ||||||
|  |               { value: 1440, text: '1440p' }, | ||||||
|  |             ]} | ||||||
|  |             name="resolution" | ||||||
|  |             isEdited={!(thumbnailConfig.jpegSize === savedConfig.jpegSize)} | ||||||
|  |           /> | ||||||
|  |         </div> | ||||||
|  |  | ||||||
|  |         <div class="ml-4"> | ||||||
|  |           <SettingButtonsRow | ||||||
|  |             on:reset={reset} | ||||||
|  |             on:save={saveSetting} | ||||||
|  |             on:reset-to-default={resetToDefault} | ||||||
|  |             showResetToDefault={!isEqual(savedConfig, defaultConfig)} | ||||||
|  |           /> | ||||||
|  |         </div> | ||||||
|  |       </form> | ||||||
|  |     </div> | ||||||
|  |   {/await} | ||||||
|  | </div> | ||||||
| @@ -2,6 +2,7 @@ | |||||||
|   import { page } from '$app/stores'; |   import { page } from '$app/stores'; | ||||||
|   import FFmpegSettings from '$lib/components/admin-page/settings/ffmpeg/ffmpeg-settings.svelte'; |   import FFmpegSettings from '$lib/components/admin-page/settings/ffmpeg/ffmpeg-settings.svelte'; | ||||||
|   import JobSettings from '$lib/components/admin-page/settings/job-settings/job-settings.svelte'; |   import JobSettings from '$lib/components/admin-page/settings/job-settings/job-settings.svelte'; | ||||||
|  |   import ThumbnailSettings from '$lib/components/admin-page/settings/thumbnail/thumbnail-settings.svelte'; | ||||||
|   import OAuthSettings from '$lib/components/admin-page/settings/oauth/oauth-settings.svelte'; |   import OAuthSettings from '$lib/components/admin-page/settings/oauth/oauth-settings.svelte'; | ||||||
|   import PasswordLoginSettings from '$lib/components/admin-page/settings/password-login/password-login-settings.svelte'; |   import PasswordLoginSettings from '$lib/components/admin-page/settings/password-login/password-login-settings.svelte'; | ||||||
|   import SettingAccordion from '$lib/components/admin-page/settings/setting-accordion.svelte'; |   import SettingAccordion from '$lib/components/admin-page/settings/setting-accordion.svelte'; | ||||||
| @@ -22,6 +23,10 @@ | |||||||
|   {#await getConfig()} |   {#await getConfig()} | ||||||
|     <LoadingSpinner /> |     <LoadingSpinner /> | ||||||
|   {:then configs} |   {:then configs} | ||||||
|  |     <SettingAccordion title="Thumbnail Settings" subtitle="Manage the resolution of thumbnail sizes"> | ||||||
|  |       <ThumbnailSettings thumbnailConfig={configs.thumbnail} /> | ||||||
|  |     </SettingAccordion> | ||||||
|  |  | ||||||
|     <SettingAccordion |     <SettingAccordion | ||||||
|       title="FFmpeg Settings" |       title="FFmpeg Settings" | ||||||
|       subtitle="Manage the resolution and encoding information of the video files" |       subtitle="Manage the resolution and encoding information of the video files" | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user