1
0
forked from Cutlery/immich

Compare commits

...

8 Commits

Author SHA1 Message Date
mertalev
bd0a8a8c73
update api 2024-03-24 02:56:49 -04:00
mertalev
9157fab650
update sql 2024-03-24 02:50:08 -04:00
mertalev
27a57a0c3a
better typing 2024-03-24 02:49:56 -04:00
mertalev
7e07b74406
replace legacy enum 2024-03-24 02:31:03 -04:00
mertalev
d451abd96c
rename to image settings 2024-03-24 02:28:40 -04:00
mertalev
a9b90787b2
update target paths, fix tests 2024-03-24 02:16:09 -04:00
mertalev
35a2aa472a
thumbnail config 2024-03-24 01:57:02 -04:00
mertalev
5a1a6493c6
rename 2024-03-24 01:52:38 -04:00
73 changed files with 771 additions and 509 deletions

View File

@ -45,7 +45,7 @@ SELECT * FROM "assets" JOIN "exif" ON "assets"."id" = "exif"."assetId" WHERE "ex
```
```sql title="Without thumbnails"
SELECT * FROM "assets" WHERE "assets"."resizePath" IS NULL OR "assets"."webpPath" IS NULL;
SELECT * FROM "assets" WHERE "assets"."previewPath" IS NULL OR "assets"."thumbnailPath" IS NULL;
```
```sql title="By type"

View File

@ -115,8 +115,10 @@ The default configuration looks like this:
"template": "{{y}}/{{y}}-{{MM}}-{{dd}}/{{filename}}"
},
"thumbnail": {
"webpSize": 250,
"jpegSize": 1440,
"thumbnailFormat": "webp",
"thumbnailSize": 250,
"previewFormat": "jpeg",
"previewSize": 1440,
"quality": 80,
"colorspace": "p3"
},

View File

@ -72,6 +72,7 @@ doc/FileChecksumResponseDto.md
doc/FileReportDto.md
doc/FileReportFixDto.md
doc/FileReportItemDto.md
doc/ImageFormat.md
doc/JobApi.md
doc/JobCommand.md
doc/JobCommandDto.md
@ -282,6 +283,7 @@ lib/model/file_checksum_response_dto.dart
lib/model/file_report_dto.dart
lib/model/file_report_fix_dto.dart
lib/model/file_report_item_dto.dart
lib/model/image_format.dart
lib/model/job_command.dart
lib/model/job_command_dto.dart
lib/model/job_counts_dto.dart
@ -459,6 +461,7 @@ test/file_checksum_response_dto_test.dart
test/file_report_dto_test.dart
test/file_report_fix_dto_test.dart
test/file_report_item_dto_test.dart
test/image_format_test.dart
test/job_api_test.dart
test/job_command_dto_test.dart
test/job_command_test.dart

View File

@ -277,6 +277,7 @@ Class | Method | HTTP request | Description
- [FileReportDto](doc//FileReportDto.md)
- [FileReportFixDto](doc//FileReportFixDto.md)
- [FileReportItemDto](doc//FileReportItemDto.md)
- [ImageFormat](doc//ImageFormat.md)
- [JobCommand](doc//JobCommand.md)
- [JobCommandDto](doc//JobCommandDto.md)
- [JobCountsDto](doc//JobCountsDto.md)

View File

@ -1040,7 +1040,7 @@ void (empty response body)
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
# **searchAssets**
> List<AssetResponseDto> searchAssets(checksum, city, country, createdAfter, createdBefore, deviceAssetId, deviceId, encodedVideoPath, id, isArchived, isEncoded, isExternal, isFavorite, isMotion, isNotInAlbum, isOffline, isReadOnly, isVisible, lensModel, libraryId, make, model, order, originalFileName, originalPath, page, personIds, resizePath, size, state, takenAfter, takenBefore, trashedAfter, trashedBefore, type, updatedAfter, updatedBefore, webpPath, withArchived, withDeleted, withExif, withPeople, withStacked)
> List<AssetResponseDto> searchAssets(checksum, city, country, createdAfter, createdBefore, deviceAssetId, deviceId, encodedVideoPath, id, isArchived, isEncoded, isExternal, isFavorite, isMotion, isNotInAlbum, isOffline, isReadOnly, isVisible, lensModel, libraryId, make, model, order, originalFileName, originalPath, page, personIds, previewPath, size, state, takenAfter, takenBefore, thumbnailPath, trashedAfter, trashedBefore, type, updatedAfter, updatedBefore, withArchived, withDeleted, withExif, withPeople, withStacked)
@ -1090,17 +1090,17 @@ final originalFileName = originalFileName_example; // String |
final originalPath = originalPath_example; // String |
final page = 8.14; // num |
final personIds = []; // List<String> |
final resizePath = resizePath_example; // String |
final previewPath = previewPath_example; // String |
final size = 8.14; // num |
final state = state_example; // String |
final takenAfter = 2013-10-20T19:20:30+01:00; // DateTime |
final takenBefore = 2013-10-20T19:20:30+01:00; // DateTime |
final thumbnailPath = thumbnailPath_example; // String |
final trashedAfter = 2013-10-20T19:20:30+01:00; // DateTime |
final trashedBefore = 2013-10-20T19:20:30+01:00; // DateTime |
final type = ; // AssetTypeEnum |
final updatedAfter = 2013-10-20T19:20:30+01:00; // DateTime |
final updatedBefore = 2013-10-20T19:20:30+01:00; // DateTime |
final webpPath = webpPath_example; // String |
final withArchived = true; // bool |
final withDeleted = true; // bool |
final withExif = true; // bool |
@ -1108,7 +1108,7 @@ final withPeople = true; // bool |
final withStacked = true; // bool |
try {
final result = api_instance.searchAssets(checksum, city, country, createdAfter, createdBefore, deviceAssetId, deviceId, encodedVideoPath, id, isArchived, isEncoded, isExternal, isFavorite, isMotion, isNotInAlbum, isOffline, isReadOnly, isVisible, lensModel, libraryId, make, model, order, originalFileName, originalPath, page, personIds, resizePath, size, state, takenAfter, takenBefore, trashedAfter, trashedBefore, type, updatedAfter, updatedBefore, webpPath, withArchived, withDeleted, withExif, withPeople, withStacked);
final result = api_instance.searchAssets(checksum, city, country, createdAfter, createdBefore, deviceAssetId, deviceId, encodedVideoPath, id, isArchived, isEncoded, isExternal, isFavorite, isMotion, isNotInAlbum, isOffline, isReadOnly, isVisible, lensModel, libraryId, make, model, order, originalFileName, originalPath, page, personIds, previewPath, size, state, takenAfter, takenBefore, thumbnailPath, trashedAfter, trashedBefore, type, updatedAfter, updatedBefore, withArchived, withDeleted, withExif, withPeople, withStacked);
print(result);
} catch (e) {
print('Exception when calling AssetApi->searchAssets: $e\n');
@ -1146,17 +1146,17 @@ Name | Type | Description | Notes
**originalPath** | **String**| | [optional]
**page** | **num**| | [optional]
**personIds** | [**List<String>**](String.md)| | [optional] [default to const []]
**resizePath** | **String**| | [optional]
**previewPath** | **String**| | [optional]
**size** | **num**| | [optional]
**state** | **String**| | [optional]
**takenAfter** | **DateTime**| | [optional]
**takenBefore** | **DateTime**| | [optional]
**thumbnailPath** | **String**| | [optional]
**trashedAfter** | **DateTime**| | [optional]
**trashedBefore** | **DateTime**| | [optional]
**type** | [**AssetTypeEnum**](.md)| | [optional]
**updatedAfter** | **DateTime**| | [optional]
**updatedBefore** | **DateTime**| | [optional]
**webpPath** | **String**| | [optional]
**withArchived** | **bool**| | [optional] [default to false]
**withDeleted** | **bool**| | [optional]
**withExif** | **bool**| | [optional]

View File

@ -12,7 +12,7 @@ Name | Type | Description | Notes
**deviceAssetId** | **String** | |
**deviceId** | **String** | |
**id** | **String** | |
**resizePath** | **String** | |
**previewPath** | **String** | |
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)

View File

@ -12,7 +12,7 @@ Name | Type | Description | Notes
**deviceId** | **String** | |
**id** | **String** | |
**object** | **String** | |
**resizePath** | **String** | |
**previewPath** | **String** | |
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)

14
mobile/openapi/doc/ImageFormat.md generated Normal file
View File

@ -0,0 +1,14 @@
# openapi.model.ImageFormat
## Load the model package
```dart
import 'package:openapi/api.dart';
```
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)

View File

@ -35,17 +35,17 @@ Name | Type | Description | Notes
**originalPath** | **String** | | [optional]
**page** | **num** | | [optional]
**personIds** | **List<String>** | | [optional] [default to const []]
**resizePath** | **String** | | [optional]
**previewPath** | **String** | | [optional]
**size** | **num** | | [optional]
**state** | **String** | | [optional]
**takenAfter** | [**DateTime**](DateTime.md) | | [optional]
**takenBefore** | [**DateTime**](DateTime.md) | | [optional]
**thumbnailPath** | **String** | | [optional]
**trashedAfter** | [**DateTime**](DateTime.md) | | [optional]
**trashedBefore** | [**DateTime**](DateTime.md) | | [optional]
**type** | [**AssetTypeEnum**](AssetTypeEnum.md) | | [optional]
**updatedAfter** | [**DateTime**](DateTime.md) | | [optional]
**updatedBefore** | [**DateTime**](DateTime.md) | | [optional]
**webpPath** | **String** | | [optional]
**withArchived** | **bool** | | [optional] [default to false]
**withDeleted** | **bool** | | [optional]
**withExif** | **bool** | | [optional]

View File

@ -9,6 +9,7 @@ import 'package:openapi/api.dart';
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**ffmpeg** | [**SystemConfigFFmpegDto**](SystemConfigFFmpegDto.md) | |
**image** | [**SystemConfigThumbnailDto**](SystemConfigThumbnailDto.md) | |
**job** | [**SystemConfigJobDto**](SystemConfigJobDto.md) | |
**library_** | [**SystemConfigLibraryDto**](SystemConfigLibraryDto.md) | |
**logging** | [**SystemConfigLoggingDto**](SystemConfigLoggingDto.md) | |
@ -21,7 +22,6 @@ Name | Type | Description | Notes
**server** | [**SystemConfigServerDto**](SystemConfigServerDto.md) | |
**storageTemplate** | [**SystemConfigStorageTemplateDto**](SystemConfigStorageTemplateDto.md) | |
**theme** | [**SystemConfigThemeDto**](SystemConfigThemeDto.md) | |
**thumbnail** | [**SystemConfigThumbnailDto**](SystemConfigThumbnailDto.md) | |
**trash** | [**SystemConfigTrashDto**](SystemConfigTrashDto.md) | |
**user** | [**SystemConfigUserDto**](SystemConfigUserDto.md) | |

View File

@ -9,9 +9,11 @@ import 'package:openapi/api.dart';
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**colorspace** | [**Colorspace**](Colorspace.md) | |
**jpegSize** | **int** | |
**previewFormat** | [**ImageFormat**](ImageFormat.md) | |
**previewSize** | **int** | |
**quality** | **int** | |
**webpSize** | **int** | |
**thumbnailFormat** | [**ImageFormat**](ImageFormat.md) | |
**thumbnailSize** | **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)

View File

@ -111,6 +111,7 @@ part 'model/file_checksum_response_dto.dart';
part 'model/file_report_dto.dart';
part 'model/file_report_fix_dto.dart';
part 'model/file_report_item_dto.dart';
part 'model/image_format.dart';
part 'model/job_command.dart';
part 'model/job_command_dto.dart';
part 'model/job_counts_dto.dart';

View File

@ -1180,7 +1180,7 @@ class AssetApi {
///
/// * [List<String>] personIds:
///
/// * [String] resizePath:
/// * [String] previewPath:
///
/// * [num] size:
///
@ -1190,6 +1190,8 @@ class AssetApi {
///
/// * [DateTime] takenBefore:
///
/// * [String] thumbnailPath:
///
/// * [DateTime] trashedAfter:
///
/// * [DateTime] trashedBefore:
@ -1200,8 +1202,6 @@ class AssetApi {
///
/// * [DateTime] updatedBefore:
///
/// * [String] webpPath:
///
/// * [bool] withArchived:
///
/// * [bool] withDeleted:
@ -1211,7 +1211,7 @@ class AssetApi {
/// * [bool] withPeople:
///
/// * [bool] withStacked:
Future<Response> searchAssetsWithHttpInfo({ String? checksum, String? city, String? country, DateTime? createdAfter, DateTime? createdBefore, String? deviceAssetId, String? deviceId, String? encodedVideoPath, String? id, bool? isArchived, bool? isEncoded, bool? isExternal, bool? isFavorite, bool? isMotion, bool? isNotInAlbum, bool? isOffline, bool? isReadOnly, bool? isVisible, String? lensModel, String? libraryId, String? make, String? model, AssetOrder? order, String? originalFileName, String? originalPath, num? page, List<String>? personIds, String? resizePath, num? size, String? state, DateTime? takenAfter, DateTime? takenBefore, DateTime? trashedAfter, DateTime? trashedBefore, AssetTypeEnum? type, DateTime? updatedAfter, DateTime? updatedBefore, String? webpPath, bool? withArchived, bool? withDeleted, bool? withExif, bool? withPeople, bool? withStacked, }) async {
Future<Response> searchAssetsWithHttpInfo({ String? checksum, String? city, String? country, DateTime? createdAfter, DateTime? createdBefore, String? deviceAssetId, String? deviceId, String? encodedVideoPath, String? id, bool? isArchived, bool? isEncoded, bool? isExternal, bool? isFavorite, bool? isMotion, bool? isNotInAlbum, bool? isOffline, bool? isReadOnly, bool? isVisible, String? lensModel, String? libraryId, String? make, String? model, AssetOrder? order, String? originalFileName, String? originalPath, num? page, List<String>? personIds, String? previewPath, num? size, String? state, DateTime? takenAfter, DateTime? takenBefore, String? thumbnailPath, DateTime? trashedAfter, DateTime? trashedBefore, AssetTypeEnum? type, DateTime? updatedAfter, DateTime? updatedBefore, bool? withArchived, bool? withDeleted, bool? withExif, bool? withPeople, bool? withStacked, }) async {
// ignore: prefer_const_declarations
final path = r'/assets';
@ -1303,8 +1303,8 @@ class AssetApi {
if (personIds != null) {
queryParams.addAll(_queryParams('multi', 'personIds', personIds));
}
if (resizePath != null) {
queryParams.addAll(_queryParams('', 'resizePath', resizePath));
if (previewPath != null) {
queryParams.addAll(_queryParams('', 'previewPath', previewPath));
}
if (size != null) {
queryParams.addAll(_queryParams('', 'size', size));
@ -1318,6 +1318,9 @@ class AssetApi {
if (takenBefore != null) {
queryParams.addAll(_queryParams('', 'takenBefore', takenBefore));
}
if (thumbnailPath != null) {
queryParams.addAll(_queryParams('', 'thumbnailPath', thumbnailPath));
}
if (trashedAfter != null) {
queryParams.addAll(_queryParams('', 'trashedAfter', trashedAfter));
}
@ -1333,9 +1336,6 @@ class AssetApi {
if (updatedBefore != null) {
queryParams.addAll(_queryParams('', 'updatedBefore', updatedBefore));
}
if (webpPath != null) {
queryParams.addAll(_queryParams('', 'webpPath', webpPath));
}
if (withArchived != null) {
queryParams.addAll(_queryParams('', 'withArchived', withArchived));
}
@ -1422,7 +1422,7 @@ class AssetApi {
///
/// * [List<String>] personIds:
///
/// * [String] resizePath:
/// * [String] previewPath:
///
/// * [num] size:
///
@ -1432,6 +1432,8 @@ class AssetApi {
///
/// * [DateTime] takenBefore:
///
/// * [String] thumbnailPath:
///
/// * [DateTime] trashedAfter:
///
/// * [DateTime] trashedBefore:
@ -1442,8 +1444,6 @@ class AssetApi {
///
/// * [DateTime] updatedBefore:
///
/// * [String] webpPath:
///
/// * [bool] withArchived:
///
/// * [bool] withDeleted:
@ -1453,8 +1453,8 @@ class AssetApi {
/// * [bool] withPeople:
///
/// * [bool] withStacked:
Future<List<AssetResponseDto>?> searchAssets({ String? checksum, String? city, String? country, DateTime? createdAfter, DateTime? createdBefore, String? deviceAssetId, String? deviceId, String? encodedVideoPath, String? id, bool? isArchived, bool? isEncoded, bool? isExternal, bool? isFavorite, bool? isMotion, bool? isNotInAlbum, bool? isOffline, bool? isReadOnly, bool? isVisible, String? lensModel, String? libraryId, String? make, String? model, AssetOrder? order, String? originalFileName, String? originalPath, num? page, List<String>? personIds, String? resizePath, num? size, String? state, DateTime? takenAfter, DateTime? takenBefore, DateTime? trashedAfter, DateTime? trashedBefore, AssetTypeEnum? type, DateTime? updatedAfter, DateTime? updatedBefore, String? webpPath, bool? withArchived, bool? withDeleted, bool? withExif, bool? withPeople, bool? withStacked, }) async {
final response = await searchAssetsWithHttpInfo( checksum: checksum, city: city, country: country, createdAfter: createdAfter, createdBefore: createdBefore, deviceAssetId: deviceAssetId, deviceId: deviceId, encodedVideoPath: encodedVideoPath, id: id, isArchived: isArchived, isEncoded: isEncoded, isExternal: isExternal, isFavorite: isFavorite, isMotion: isMotion, isNotInAlbum: isNotInAlbum, isOffline: isOffline, isReadOnly: isReadOnly, isVisible: isVisible, lensModel: lensModel, libraryId: libraryId, make: make, model: model, order: order, originalFileName: originalFileName, originalPath: originalPath, page: page, personIds: personIds, resizePath: resizePath, size: size, state: state, takenAfter: takenAfter, takenBefore: takenBefore, trashedAfter: trashedAfter, trashedBefore: trashedBefore, type: type, updatedAfter: updatedAfter, updatedBefore: updatedBefore, webpPath: webpPath, withArchived: withArchived, withDeleted: withDeleted, withExif: withExif, withPeople: withPeople, withStacked: withStacked, );
Future<List<AssetResponseDto>?> searchAssets({ String? checksum, String? city, String? country, DateTime? createdAfter, DateTime? createdBefore, String? deviceAssetId, String? deviceId, String? encodedVideoPath, String? id, bool? isArchived, bool? isEncoded, bool? isExternal, bool? isFavorite, bool? isMotion, bool? isNotInAlbum, bool? isOffline, bool? isReadOnly, bool? isVisible, String? lensModel, String? libraryId, String? make, String? model, AssetOrder? order, String? originalFileName, String? originalPath, num? page, List<String>? personIds, String? previewPath, num? size, String? state, DateTime? takenAfter, DateTime? takenBefore, String? thumbnailPath, DateTime? trashedAfter, DateTime? trashedBefore, AssetTypeEnum? type, DateTime? updatedAfter, DateTime? updatedBefore, bool? withArchived, bool? withDeleted, bool? withExif, bool? withPeople, bool? withStacked, }) async {
final response = await searchAssetsWithHttpInfo( checksum: checksum, city: city, country: country, createdAfter: createdAfter, createdBefore: createdBefore, deviceAssetId: deviceAssetId, deviceId: deviceId, encodedVideoPath: encodedVideoPath, id: id, isArchived: isArchived, isEncoded: isEncoded, isExternal: isExternal, isFavorite: isFavorite, isMotion: isMotion, isNotInAlbum: isNotInAlbum, isOffline: isOffline, isReadOnly: isReadOnly, isVisible: isVisible, lensModel: lensModel, libraryId: libraryId, make: make, model: model, order: order, originalFileName: originalFileName, originalPath: originalPath, page: page, personIds: personIds, previewPath: previewPath, size: size, state: state, takenAfter: takenAfter, takenBefore: takenBefore, thumbnailPath: thumbnailPath, trashedAfter: trashedAfter, trashedBefore: trashedBefore, type: type, updatedAfter: updatedAfter, updatedBefore: updatedBefore, withArchived: withArchived, withDeleted: withDeleted, withExif: withExif, withPeople: withPeople, withStacked: withStacked, );
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}

View File

@ -304,6 +304,8 @@ class ApiClient {
return FileReportFixDto.fromJson(value);
case 'FileReportItemDto':
return FileReportItemDto.fromJson(value);
case 'ImageFormat':
return ImageFormatTypeTransformer().decode(value);
case 'JobCommand':
return JobCommandTypeTransformer().decode(value);
case 'JobCommandDto':

View File

@ -79,6 +79,9 @@ String parameterToString(dynamic value) {
if (value is EntityType) {
return EntityTypeTypeTransformer().encode(value).toString();
}
if (value is ImageFormat) {
return ImageFormatTypeTransformer().encode(value).toString();
}
if (value is JobCommand) {
return JobCommandTypeTransformer().encode(value).toString();
}

View File

@ -17,7 +17,7 @@ class CuratedLocationsResponseDto {
required this.deviceAssetId,
required this.deviceId,
required this.id,
required this.resizePath,
required this.previewPath,
});
String city;
@ -28,7 +28,7 @@ class CuratedLocationsResponseDto {
String id;
String resizePath;
String previewPath;
@override
bool operator ==(Object other) => identical(this, other) || other is CuratedLocationsResponseDto &&
@ -36,7 +36,7 @@ class CuratedLocationsResponseDto {
other.deviceAssetId == deviceAssetId &&
other.deviceId == deviceId &&
other.id == id &&
other.resizePath == resizePath;
other.previewPath == previewPath;
@override
int get hashCode =>
@ -45,10 +45,10 @@ class CuratedLocationsResponseDto {
(deviceAssetId.hashCode) +
(deviceId.hashCode) +
(id.hashCode) +
(resizePath.hashCode);
(previewPath.hashCode);
@override
String toString() => 'CuratedLocationsResponseDto[city=$city, deviceAssetId=$deviceAssetId, deviceId=$deviceId, id=$id, resizePath=$resizePath]';
String toString() => 'CuratedLocationsResponseDto[city=$city, deviceAssetId=$deviceAssetId, deviceId=$deviceId, id=$id, previewPath=$previewPath]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
@ -56,7 +56,7 @@ class CuratedLocationsResponseDto {
json[r'deviceAssetId'] = this.deviceAssetId;
json[r'deviceId'] = this.deviceId;
json[r'id'] = this.id;
json[r'resizePath'] = this.resizePath;
json[r'previewPath'] = this.previewPath;
return json;
}
@ -72,7 +72,7 @@ class CuratedLocationsResponseDto {
deviceAssetId: mapValueOfType<String>(json, r'deviceAssetId')!,
deviceId: mapValueOfType<String>(json, r'deviceId')!,
id: mapValueOfType<String>(json, r'id')!,
resizePath: mapValueOfType<String>(json, r'resizePath')!,
previewPath: mapValueOfType<String>(json, r'previewPath')!,
);
}
return null;
@ -124,7 +124,7 @@ class CuratedLocationsResponseDto {
'deviceAssetId',
'deviceId',
'id',
'resizePath',
'previewPath',
};
}

View File

@ -17,7 +17,7 @@ class CuratedObjectsResponseDto {
required this.deviceId,
required this.id,
required this.object,
required this.resizePath,
required this.previewPath,
});
String deviceAssetId;
@ -28,7 +28,7 @@ class CuratedObjectsResponseDto {
String object;
String resizePath;
String previewPath;
@override
bool operator ==(Object other) => identical(this, other) || other is CuratedObjectsResponseDto &&
@ -36,7 +36,7 @@ class CuratedObjectsResponseDto {
other.deviceId == deviceId &&
other.id == id &&
other.object == object &&
other.resizePath == resizePath;
other.previewPath == previewPath;
@override
int get hashCode =>
@ -45,10 +45,10 @@ class CuratedObjectsResponseDto {
(deviceId.hashCode) +
(id.hashCode) +
(object.hashCode) +
(resizePath.hashCode);
(previewPath.hashCode);
@override
String toString() => 'CuratedObjectsResponseDto[deviceAssetId=$deviceAssetId, deviceId=$deviceId, id=$id, object=$object, resizePath=$resizePath]';
String toString() => 'CuratedObjectsResponseDto[deviceAssetId=$deviceAssetId, deviceId=$deviceId, id=$id, object=$object, previewPath=$previewPath]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
@ -56,7 +56,7 @@ class CuratedObjectsResponseDto {
json[r'deviceId'] = this.deviceId;
json[r'id'] = this.id;
json[r'object'] = this.object;
json[r'resizePath'] = this.resizePath;
json[r'previewPath'] = this.previewPath;
return json;
}
@ -72,7 +72,7 @@ class CuratedObjectsResponseDto {
deviceId: mapValueOfType<String>(json, r'deviceId')!,
id: mapValueOfType<String>(json, r'id')!,
object: mapValueOfType<String>(json, r'object')!,
resizePath: mapValueOfType<String>(json, r'resizePath')!,
previewPath: mapValueOfType<String>(json, r'previewPath')!,
);
}
return null;
@ -124,7 +124,7 @@ class CuratedObjectsResponseDto {
'deviceId',
'id',
'object',
'resizePath',
'previewPath',
};
}

View File

@ -0,0 +1,85 @@
//
// 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 ImageFormat {
/// Instantiate a new enum with the provided [value].
const ImageFormat._(this.value);
/// The underlying value of this enum member.
final String value;
@override
String toString() => value;
String toJson() => value;
static const jpeg = ImageFormat._(r'jpeg');
static const webp = ImageFormat._(r'webp');
/// List of all possible values in this [enum][ImageFormat].
static const values = <ImageFormat>[
jpeg,
webp,
];
static ImageFormat? fromJson(dynamic value) => ImageFormatTypeTransformer().decode(value);
static List<ImageFormat> listFromJson(dynamic json, {bool growable = false,}) {
final result = <ImageFormat>[];
if (json is List && json.isNotEmpty) {
for (final row in json) {
final value = ImageFormat.fromJson(row);
if (value != null) {
result.add(value);
}
}
}
return result.toList(growable: growable);
}
}
/// Transformation class that can [encode] an instance of [ImageFormat] to String,
/// and [decode] dynamic data back to [ImageFormat].
class ImageFormatTypeTransformer {
factory ImageFormatTypeTransformer() => _instance ??= const ImageFormatTypeTransformer._();
const ImageFormatTypeTransformer._();
String encode(ImageFormat data) => data.value;
/// Decodes a [dynamic value][data] to a ImageFormat.
///
/// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully,
/// then null is returned. However, if [allowNull] is false and the [dynamic value][data]
/// cannot be decoded successfully, then an [UnimplementedError] is thrown.
///
/// The [allowNull] is very handy when an API changes and a new enum value is added or removed,
/// and users are still using an old app with the old code.
ImageFormat? decode(dynamic data, {bool allowNull = true}) {
if (data != null) {
switch (data) {
case r'jpeg': return ImageFormat.jpeg;
case r'webp': return ImageFormat.webp;
default:
if (!allowNull) {
throw ArgumentError('Unknown enum value to decode: $data');
}
}
}
return null;
}
/// Singleton [ImageFormatTypeTransformer] instance.
static ImageFormatTypeTransformer? _instance;
}

View File

@ -40,17 +40,17 @@ class MetadataSearchDto {
this.originalPath,
this.page,
this.personIds = const [],
this.resizePath,
this.previewPath,
this.size,
this.state,
this.takenAfter,
this.takenBefore,
this.thumbnailPath,
this.trashedAfter,
this.trashedBefore,
this.type,
this.updatedAfter,
this.updatedBefore,
this.webpPath,
this.withArchived = false,
this.withDeleted,
this.withExif,
@ -274,7 +274,7 @@ class MetadataSearchDto {
/// source code must fall back to having a nullable type.
/// Consider adding a "default:" property in the specification file to hide this note.
///
String? resizePath;
String? previewPath;
///
/// Please note: This property should have been non-nullable! Since the specification file
@ -308,6 +308,14 @@ class MetadataSearchDto {
///
DateTime? takenBefore;
///
/// Please note: This property should have been non-nullable! Since the specification file
/// does not include a default value (using the "default:" property), however, the generated
/// source code must fall back to having a nullable type.
/// Consider adding a "default:" property in the specification file to hide this note.
///
String? thumbnailPath;
///
/// Please note: This property should have been non-nullable! Since the specification file
/// does not include a default value (using the "default:" property), however, the generated
@ -348,14 +356,6 @@ class MetadataSearchDto {
///
DateTime? updatedBefore;
///
/// Please note: This property should have been non-nullable! Since the specification file
/// does not include a default value (using the "default:" property), however, the generated
/// source code must fall back to having a nullable type.
/// Consider adding a "default:" property in the specification file to hide this note.
///
String? webpPath;
bool withArchived;
///
@ -419,17 +419,17 @@ class MetadataSearchDto {
other.originalPath == originalPath &&
other.page == page &&
_deepEquality.equals(other.personIds, personIds) &&
other.resizePath == resizePath &&
other.previewPath == previewPath &&
other.size == size &&
other.state == state &&
other.takenAfter == takenAfter &&
other.takenBefore == takenBefore &&
other.thumbnailPath == thumbnailPath &&
other.trashedAfter == trashedAfter &&
other.trashedBefore == trashedBefore &&
other.type == type &&
other.updatedAfter == updatedAfter &&
other.updatedBefore == updatedBefore &&
other.webpPath == webpPath &&
other.withArchived == withArchived &&
other.withDeleted == withDeleted &&
other.withExif == withExif &&
@ -466,17 +466,17 @@ class MetadataSearchDto {
(originalPath == null ? 0 : originalPath!.hashCode) +
(page == null ? 0 : page!.hashCode) +
(personIds.hashCode) +
(resizePath == null ? 0 : resizePath!.hashCode) +
(previewPath == null ? 0 : previewPath!.hashCode) +
(size == null ? 0 : size!.hashCode) +
(state == null ? 0 : state!.hashCode) +
(takenAfter == null ? 0 : takenAfter!.hashCode) +
(takenBefore == null ? 0 : takenBefore!.hashCode) +
(thumbnailPath == null ? 0 : thumbnailPath!.hashCode) +
(trashedAfter == null ? 0 : trashedAfter!.hashCode) +
(trashedBefore == null ? 0 : trashedBefore!.hashCode) +
(type == null ? 0 : type!.hashCode) +
(updatedAfter == null ? 0 : updatedAfter!.hashCode) +
(updatedBefore == null ? 0 : updatedBefore!.hashCode) +
(webpPath == null ? 0 : webpPath!.hashCode) +
(withArchived.hashCode) +
(withDeleted == null ? 0 : withDeleted!.hashCode) +
(withExif == null ? 0 : withExif!.hashCode) +
@ -484,7 +484,7 @@ class MetadataSearchDto {
(withStacked == null ? 0 : withStacked!.hashCode);
@override
String toString() => 'MetadataSearchDto[checksum=$checksum, city=$city, country=$country, createdAfter=$createdAfter, createdBefore=$createdBefore, deviceAssetId=$deviceAssetId, deviceId=$deviceId, encodedVideoPath=$encodedVideoPath, id=$id, isArchived=$isArchived, isEncoded=$isEncoded, isExternal=$isExternal, isFavorite=$isFavorite, isMotion=$isMotion, isNotInAlbum=$isNotInAlbum, isOffline=$isOffline, isReadOnly=$isReadOnly, isVisible=$isVisible, lensModel=$lensModel, libraryId=$libraryId, make=$make, model=$model, order=$order, originalFileName=$originalFileName, originalPath=$originalPath, page=$page, personIds=$personIds, resizePath=$resizePath, size=$size, state=$state, takenAfter=$takenAfter, takenBefore=$takenBefore, trashedAfter=$trashedAfter, trashedBefore=$trashedBefore, type=$type, updatedAfter=$updatedAfter, updatedBefore=$updatedBefore, webpPath=$webpPath, withArchived=$withArchived, withDeleted=$withDeleted, withExif=$withExif, withPeople=$withPeople, withStacked=$withStacked]';
String toString() => 'MetadataSearchDto[checksum=$checksum, city=$city, country=$country, createdAfter=$createdAfter, createdBefore=$createdBefore, deviceAssetId=$deviceAssetId, deviceId=$deviceId, encodedVideoPath=$encodedVideoPath, id=$id, isArchived=$isArchived, isEncoded=$isEncoded, isExternal=$isExternal, isFavorite=$isFavorite, isMotion=$isMotion, isNotInAlbum=$isNotInAlbum, isOffline=$isOffline, isReadOnly=$isReadOnly, isVisible=$isVisible, lensModel=$lensModel, libraryId=$libraryId, make=$make, model=$model, order=$order, originalFileName=$originalFileName, originalPath=$originalPath, page=$page, personIds=$personIds, previewPath=$previewPath, size=$size, state=$state, takenAfter=$takenAfter, takenBefore=$takenBefore, thumbnailPath=$thumbnailPath, trashedAfter=$trashedAfter, trashedBefore=$trashedBefore, type=$type, updatedAfter=$updatedAfter, updatedBefore=$updatedBefore, withArchived=$withArchived, withDeleted=$withDeleted, withExif=$withExif, withPeople=$withPeople, withStacked=$withStacked]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
@ -619,10 +619,10 @@ class MetadataSearchDto {
// json[r'page'] = null;
}
json[r'personIds'] = this.personIds;
if (this.resizePath != null) {
json[r'resizePath'] = this.resizePath;
if (this.previewPath != null) {
json[r'previewPath'] = this.previewPath;
} else {
// json[r'resizePath'] = null;
// json[r'previewPath'] = null;
}
if (this.size != null) {
json[r'size'] = this.size;
@ -644,6 +644,11 @@ class MetadataSearchDto {
} else {
// json[r'takenBefore'] = null;
}
if (this.thumbnailPath != null) {
json[r'thumbnailPath'] = this.thumbnailPath;
} else {
// json[r'thumbnailPath'] = null;
}
if (this.trashedAfter != null) {
json[r'trashedAfter'] = this.trashedAfter!.toUtc().toIso8601String();
} else {
@ -668,11 +673,6 @@ class MetadataSearchDto {
json[r'updatedBefore'] = this.updatedBefore!.toUtc().toIso8601String();
} else {
// json[r'updatedBefore'] = null;
}
if (this.webpPath != null) {
json[r'webpPath'] = this.webpPath;
} else {
// json[r'webpPath'] = null;
}
json[r'withArchived'] = this.withArchived;
if (this.withDeleted != null) {
@ -735,17 +735,17 @@ class MetadataSearchDto {
personIds: json[r'personIds'] is Iterable
? (json[r'personIds'] as Iterable).cast<String>().toList(growable: false)
: const [],
resizePath: mapValueOfType<String>(json, r'resizePath'),
previewPath: mapValueOfType<String>(json, r'previewPath'),
size: num.parse('${json[r'size']}'),
state: mapValueOfType<String>(json, r'state'),
takenAfter: mapDateTime(json, r'takenAfter', r''),
takenBefore: mapDateTime(json, r'takenBefore', r''),
thumbnailPath: mapValueOfType<String>(json, r'thumbnailPath'),
trashedAfter: mapDateTime(json, r'trashedAfter', r''),
trashedBefore: mapDateTime(json, r'trashedBefore', r''),
type: AssetTypeEnum.fromJson(json[r'type']),
updatedAfter: mapDateTime(json, r'updatedAfter', r''),
updatedBefore: mapDateTime(json, r'updatedBefore', r''),
webpPath: mapValueOfType<String>(json, r'webpPath'),
withArchived: mapValueOfType<bool>(json, r'withArchived') ?? false,
withDeleted: mapValueOfType<bool>(json, r'withDeleted'),
withExif: mapValueOfType<bool>(json, r'withExif'),

View File

@ -24,8 +24,8 @@ class PathType {
String toJson() => value;
static const original = PathType._(r'original');
static const jpegThumbnail = PathType._(r'jpeg_thumbnail');
static const webpThumbnail = PathType._(r'webp_thumbnail');
static const preview = PathType._(r'preview');
static const thumbnail = PathType._(r'thumbnail');
static const encodedVideo = PathType._(r'encoded_video');
static const sidecar = PathType._(r'sidecar');
static const face = PathType._(r'face');
@ -34,8 +34,8 @@ class PathType {
/// List of all possible values in this [enum][PathType].
static const values = <PathType>[
original,
jpegThumbnail,
webpThumbnail,
preview,
thumbnail,
encodedVideo,
sidecar,
face,
@ -79,8 +79,8 @@ class PathTypeTypeTransformer {
if (data != null) {
switch (data) {
case r'original': return PathType.original;
case r'jpeg_thumbnail': return PathType.jpegThumbnail;
case r'webp_thumbnail': return PathType.webpThumbnail;
case r'preview': return PathType.preview;
case r'thumbnail': return PathType.thumbnail;
case r'encoded_video': return PathType.encodedVideo;
case r'sidecar': return PathType.sidecar;
case r'face': return PathType.face;

View File

@ -14,6 +14,7 @@ class SystemConfigDto {
/// Returns a new [SystemConfigDto] instance.
SystemConfigDto({
required this.ffmpeg,
required this.image,
required this.job,
required this.library_,
required this.logging,
@ -26,13 +27,14 @@ class SystemConfigDto {
required this.server,
required this.storageTemplate,
required this.theme,
required this.thumbnail,
required this.trash,
required this.user,
});
SystemConfigFFmpegDto ffmpeg;
SystemConfigThumbnailDto image;
SystemConfigJobDto job;
SystemConfigLibraryDto library_;
@ -57,8 +59,6 @@ class SystemConfigDto {
SystemConfigThemeDto theme;
SystemConfigThumbnailDto thumbnail;
SystemConfigTrashDto trash;
SystemConfigUserDto user;
@ -66,6 +66,7 @@ class SystemConfigDto {
@override
bool operator ==(Object other) => identical(this, other) || other is SystemConfigDto &&
other.ffmpeg == ffmpeg &&
other.image == image &&
other.job == job &&
other.library_ == library_ &&
other.logging == logging &&
@ -78,7 +79,6 @@ class SystemConfigDto {
other.server == server &&
other.storageTemplate == storageTemplate &&
other.theme == theme &&
other.thumbnail == thumbnail &&
other.trash == trash &&
other.user == user;
@ -86,6 +86,7 @@ class SystemConfigDto {
int get hashCode =>
// ignore: unnecessary_parenthesis
(ffmpeg.hashCode) +
(image.hashCode) +
(job.hashCode) +
(library_.hashCode) +
(logging.hashCode) +
@ -98,16 +99,16 @@ class SystemConfigDto {
(server.hashCode) +
(storageTemplate.hashCode) +
(theme.hashCode) +
(thumbnail.hashCode) +
(trash.hashCode) +
(user.hashCode);
@override
String toString() => 'SystemConfigDto[ffmpeg=$ffmpeg, job=$job, library_=$library_, logging=$logging, machineLearning=$machineLearning, map=$map, newVersionCheck=$newVersionCheck, oauth=$oauth, passwordLogin=$passwordLogin, reverseGeocoding=$reverseGeocoding, server=$server, storageTemplate=$storageTemplate, theme=$theme, thumbnail=$thumbnail, trash=$trash, user=$user]';
String toString() => 'SystemConfigDto[ffmpeg=$ffmpeg, image=$image, job=$job, library_=$library_, logging=$logging, machineLearning=$machineLearning, map=$map, newVersionCheck=$newVersionCheck, oauth=$oauth, passwordLogin=$passwordLogin, reverseGeocoding=$reverseGeocoding, server=$server, storageTemplate=$storageTemplate, theme=$theme, trash=$trash, user=$user]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
json[r'ffmpeg'] = this.ffmpeg;
json[r'image'] = this.image;
json[r'job'] = this.job;
json[r'library'] = this.library_;
json[r'logging'] = this.logging;
@ -120,7 +121,6 @@ class SystemConfigDto {
json[r'server'] = this.server;
json[r'storageTemplate'] = this.storageTemplate;
json[r'theme'] = this.theme;
json[r'thumbnail'] = this.thumbnail;
json[r'trash'] = this.trash;
json[r'user'] = this.user;
return json;
@ -135,6 +135,7 @@ class SystemConfigDto {
return SystemConfigDto(
ffmpeg: SystemConfigFFmpegDto.fromJson(json[r'ffmpeg'])!,
image: SystemConfigThumbnailDto.fromJson(json[r'image'])!,
job: SystemConfigJobDto.fromJson(json[r'job'])!,
library_: SystemConfigLibraryDto.fromJson(json[r'library'])!,
logging: SystemConfigLoggingDto.fromJson(json[r'logging'])!,
@ -147,7 +148,6 @@ class SystemConfigDto {
server: SystemConfigServerDto.fromJson(json[r'server'])!,
storageTemplate: SystemConfigStorageTemplateDto.fromJson(json[r'storageTemplate'])!,
theme: SystemConfigThemeDto.fromJson(json[r'theme'])!,
thumbnail: SystemConfigThumbnailDto.fromJson(json[r'thumbnail'])!,
trash: SystemConfigTrashDto.fromJson(json[r'trash'])!,
user: SystemConfigUserDto.fromJson(json[r'user'])!,
);
@ -198,6 +198,7 @@ class SystemConfigDto {
/// The list of required keys that must be present in a JSON.
static const requiredKeys = <String>{
'ffmpeg',
'image',
'job',
'library',
'logging',
@ -210,7 +211,6 @@ class SystemConfigDto {
'server',
'storageTemplate',
'theme',
'thumbnail',
'trash',
'user',
};

View File

@ -14,43 +14,55 @@ class SystemConfigThumbnailDto {
/// Returns a new [SystemConfigThumbnailDto] instance.
SystemConfigThumbnailDto({
required this.colorspace,
required this.jpegSize,
required this.previewFormat,
required this.previewSize,
required this.quality,
required this.webpSize,
required this.thumbnailFormat,
required this.thumbnailSize,
});
Colorspace colorspace;
int jpegSize;
ImageFormat previewFormat;
int previewSize;
int quality;
int webpSize;
ImageFormat thumbnailFormat;
int thumbnailSize;
@override
bool operator ==(Object other) => identical(this, other) || other is SystemConfigThumbnailDto &&
other.colorspace == colorspace &&
other.jpegSize == jpegSize &&
other.previewFormat == previewFormat &&
other.previewSize == previewSize &&
other.quality == quality &&
other.webpSize == webpSize;
other.thumbnailFormat == thumbnailFormat &&
other.thumbnailSize == thumbnailSize;
@override
int get hashCode =>
// ignore: unnecessary_parenthesis
(colorspace.hashCode) +
(jpegSize.hashCode) +
(previewFormat.hashCode) +
(previewSize.hashCode) +
(quality.hashCode) +
(webpSize.hashCode);
(thumbnailFormat.hashCode) +
(thumbnailSize.hashCode);
@override
String toString() => 'SystemConfigThumbnailDto[colorspace=$colorspace, jpegSize=$jpegSize, quality=$quality, webpSize=$webpSize]';
String toString() => 'SystemConfigThumbnailDto[colorspace=$colorspace, previewFormat=$previewFormat, previewSize=$previewSize, quality=$quality, thumbnailFormat=$thumbnailFormat, thumbnailSize=$thumbnailSize]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
json[r'colorspace'] = this.colorspace;
json[r'jpegSize'] = this.jpegSize;
json[r'previewFormat'] = this.previewFormat;
json[r'previewSize'] = this.previewSize;
json[r'quality'] = this.quality;
json[r'webpSize'] = this.webpSize;
json[r'thumbnailFormat'] = this.thumbnailFormat;
json[r'thumbnailSize'] = this.thumbnailSize;
return json;
}
@ -63,9 +75,11 @@ class SystemConfigThumbnailDto {
return SystemConfigThumbnailDto(
colorspace: Colorspace.fromJson(json[r'colorspace'])!,
jpegSize: mapValueOfType<int>(json, r'jpegSize')!,
previewFormat: ImageFormat.fromJson(json[r'previewFormat'])!,
previewSize: mapValueOfType<int>(json, r'previewSize')!,
quality: mapValueOfType<int>(json, r'quality')!,
webpSize: mapValueOfType<int>(json, r'webpSize')!,
thumbnailFormat: ImageFormat.fromJson(json[r'thumbnailFormat'])!,
thumbnailSize: mapValueOfType<int>(json, r'thumbnailSize')!,
);
}
return null;
@ -114,9 +128,11 @@ class SystemConfigThumbnailDto {
/// The list of required keys that must be present in a JSON.
static const requiredKeys = <String>{
'colorspace',
'jpegSize',
'previewFormat',
'previewSize',
'quality',
'webpSize',
'thumbnailFormat',
'thumbnailSize',
};
}

View File

@ -23,13 +23,13 @@ class ThumbnailFormat {
String toJson() => value;
static const JPEG = ThumbnailFormat._(r'JPEG');
static const WEBP = ThumbnailFormat._(r'WEBP');
static const jpeg = ThumbnailFormat._(r'jpeg');
static const webp = ThumbnailFormat._(r'webp');
/// List of all possible values in this [enum][ThumbnailFormat].
static const values = <ThumbnailFormat>[
JPEG,
WEBP,
jpeg,
webp,
];
static ThumbnailFormat? fromJson(dynamic value) => ThumbnailFormatTypeTransformer().decode(value);
@ -68,8 +68,8 @@ class ThumbnailFormatTypeTransformer {
ThumbnailFormat? decode(dynamic data, {bool allowNull = true}) {
if (data != null) {
switch (data) {
case r'JPEG': return ThumbnailFormat.JPEG;
case r'WEBP': return ThumbnailFormat.WEBP;
case r'jpeg': return ThumbnailFormat.jpeg;
case r'webp': return ThumbnailFormat.webp;
default:
if (!allowNull) {
throw ArgumentError('Unknown enum value to decode: $data');

View File

@ -110,7 +110,7 @@ void main() {
// TODO
});
//Future<List<AssetResponseDto>> searchAssets({ String checksum, String city, String country, DateTime createdAfter, DateTime createdBefore, String deviceAssetId, String deviceId, String encodedVideoPath, String id, bool isArchived, bool isEncoded, bool isExternal, bool isFavorite, bool isMotion, bool isNotInAlbum, bool isOffline, bool isReadOnly, bool isVisible, String lensModel, String libraryId, String make, String model, AssetOrder order, String originalFileName, String originalPath, num page, List<String> personIds, String resizePath, num size, String state, DateTime takenAfter, DateTime takenBefore, DateTime trashedAfter, DateTime trashedBefore, AssetTypeEnum type, DateTime updatedAfter, DateTime updatedBefore, String webpPath, bool withArchived, bool withDeleted, bool withExif, bool withPeople, bool withStacked }) async
//Future<List<AssetResponseDto>> searchAssets({ String checksum, String city, String country, DateTime createdAfter, DateTime createdBefore, String deviceAssetId, String deviceId, String encodedVideoPath, String id, bool isArchived, bool isEncoded, bool isExternal, bool isFavorite, bool isMotion, bool isNotInAlbum, bool isOffline, bool isReadOnly, bool isVisible, String lensModel, String libraryId, String make, String model, AssetOrder order, String originalFileName, String originalPath, num page, List<String> personIds, String previewPath, num size, String state, DateTime takenAfter, DateTime takenBefore, String thumbnailPath, DateTime trashedAfter, DateTime trashedBefore, AssetTypeEnum type, DateTime updatedAfter, DateTime updatedBefore, bool withArchived, bool withDeleted, bool withExif, bool withPeople, bool withStacked }) async
test('test searchAssets', () async {
// TODO
});

View File

@ -36,8 +36,8 @@ void main() {
// TODO
});
// String resizePath
test('to test the property `resizePath`', () async {
// String previewPath
test('to test the property `previewPath`', () async {
// TODO
});

View File

@ -36,8 +36,8 @@ void main() {
// TODO
});
// String resizePath
test('to test the property `resizePath`', () async {
// String previewPath
test('to test the property `previewPath`', () async {
// TODO
});

View File

@ -0,0 +1,21 @@
//
// 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 ImageFormat
void main() {
group('test ImageFormat', () {
});
}

View File

@ -151,8 +151,8 @@ void main() {
// TODO
});
// String resizePath
test('to test the property `resizePath`', () async {
// String previewPath
test('to test the property `previewPath`', () async {
// TODO
});
@ -176,6 +176,11 @@ void main() {
// TODO
});
// String thumbnailPath
test('to test the property `thumbnailPath`', () async {
// TODO
});
// DateTime trashedAfter
test('to test the property `trashedAfter`', () async {
// TODO
@ -201,11 +206,6 @@ void main() {
// TODO
});
// String webpPath
test('to test the property `webpPath`', () async {
// TODO
});
// bool withArchived (default value: false)
test('to test the property `withArchived`', () async {
// TODO

View File

@ -21,6 +21,11 @@ void main() {
// TODO
});
// SystemConfigThumbnailDto image
test('to test the property `image`', () async {
// TODO
});
// SystemConfigJobDto job
test('to test the property `job`', () async {
// TODO
@ -81,11 +86,6 @@ void main() {
// TODO
});
// SystemConfigThumbnailDto thumbnail
test('to test the property `thumbnail`', () async {
// TODO
});
// SystemConfigTrashDto trash
test('to test the property `trash`', () async {
// TODO

View File

@ -21,8 +21,13 @@ void main() {
// TODO
});
// int jpegSize
test('to test the property `jpegSize`', () async {
// ImageFormat previewFormat
test('to test the property `previewFormat`', () async {
// TODO
});
// int previewSize
test('to test the property `previewSize`', () async {
// TODO
});
@ -31,8 +36,13 @@ void main() {
// TODO
});
// int webpSize
test('to test the property `webpSize`', () async {
// ImageFormat thumbnailFormat
test('to test the property `thumbnailFormat`', () async {
// TODO
});
// int thumbnailSize
test('to test the property `thumbnailSize`', () async {
// TODO
});

View File

@ -2382,7 +2382,7 @@
}
},
{
"name": "resizePath",
"name": "previewPath",
"required": false,
"in": "query",
"schema": {
@ -2423,6 +2423,14 @@
"type": "string"
}
},
{
"name": "thumbnailPath",
"required": false,
"in": "query",
"schema": {
"type": "string"
}
},
{
"name": "trashedAfter",
"required": false,
@ -2467,14 +2475,6 @@
"type": "string"
}
},
{
"name": "webpPath",
"required": false,
"in": "query",
"schema": {
"type": "string"
}
},
{
"name": "withArchived",
"required": false,
@ -7776,7 +7776,7 @@
"id": {
"type": "string"
},
"resizePath": {
"previewPath": {
"type": "string"
}
},
@ -7785,7 +7785,7 @@
"deviceAssetId",
"deviceId",
"id",
"resizePath"
"previewPath"
],
"type": "object"
},
@ -7803,7 +7803,7 @@
"object": {
"type": "string"
},
"resizePath": {
"previewPath": {
"type": "string"
}
},
@ -7812,7 +7812,7 @@
"deviceId",
"id",
"object",
"resizePath"
"previewPath"
],
"type": "object"
},
@ -8106,6 +8106,13 @@
],
"type": "object"
},
"ImageFormat": {
"enum": [
"jpeg",
"webp"
],
"type": "string"
},
"JobCommand": {
"enum": [
"start",
@ -8542,7 +8549,7 @@
},
"type": "array"
},
"resizePath": {
"previewPath": {
"type": "string"
},
"size": {
@ -8559,6 +8566,9 @@
"format": "date-time",
"type": "string"
},
"thumbnailPath": {
"type": "string"
},
"trashedAfter": {
"format": "date-time",
"type": "string"
@ -8578,9 +8588,6 @@
"format": "date-time",
"type": "string"
},
"webpPath": {
"type": "string"
},
"withArchived": {
"default": false,
"type": "boolean"
@ -8733,8 +8740,8 @@
"PathType": {
"enum": [
"original",
"jpeg_thumbnail",
"webp_thumbnail",
"preview",
"thumbnail",
"encoded_video",
"sidecar",
"face",
@ -9730,6 +9737,9 @@
"ffmpeg": {
"$ref": "#/components/schemas/SystemConfigFFmpegDto"
},
"image": {
"$ref": "#/components/schemas/SystemConfigThumbnailDto"
},
"job": {
"$ref": "#/components/schemas/SystemConfigJobDto"
},
@ -9766,9 +9776,6 @@
"theme": {
"$ref": "#/components/schemas/SystemConfigThemeDto"
},
"thumbnail": {
"$ref": "#/components/schemas/SystemConfigThumbnailDto"
},
"trash": {
"$ref": "#/components/schemas/SystemConfigTrashDto"
},
@ -9778,6 +9785,7 @@
},
"required": [
"ffmpeg",
"image",
"job",
"library",
"logging",
@ -9790,7 +9798,6 @@
"server",
"storageTemplate",
"theme",
"thumbnail",
"trash",
"user"
],
@ -10243,21 +10250,29 @@
"colorspace": {
"$ref": "#/components/schemas/Colorspace"
},
"jpegSize": {
"previewFormat": {
"$ref": "#/components/schemas/ImageFormat"
},
"previewSize": {
"type": "integer"
},
"quality": {
"type": "integer"
},
"webpSize": {
"thumbnailFormat": {
"$ref": "#/components/schemas/ImageFormat"
},
"thumbnailSize": {
"type": "integer"
}
},
"required": [
"colorspace",
"jpegSize",
"previewFormat",
"previewSize",
"quality",
"webpSize"
"thumbnailFormat",
"thumbnailSize"
],
"type": "object"
},
@ -10320,8 +10335,8 @@
},
"ThumbnailFormat": {
"enum": [
"JPEG",
"WEBP"
"jpeg",
"webp"
],
"type": "string"
},

View File

@ -242,14 +242,14 @@ export type CuratedLocationsResponseDto = {
deviceAssetId: string;
deviceId: string;
id: string;
resizePath: string;
previewPath: string;
};
export type CuratedObjectsResponseDto = {
deviceAssetId: string;
deviceId: string;
id: string;
"object": string;
resizePath: string;
previewPath: string;
};
export type CheckExistingAssetsDto = {
deviceAssetIds: string[];
@ -643,17 +643,17 @@ export type MetadataSearchDto = {
originalPath?: string;
page?: number;
personIds?: string[];
resizePath?: string;
previewPath?: string;
size?: number;
state?: string;
takenAfter?: string;
takenBefore?: string;
thumbnailPath?: string;
trashedAfter?: string;
trashedBefore?: string;
"type"?: AssetTypeEnum;
updatedAfter?: string;
updatedBefore?: string;
webpPath?: string;
withArchived?: boolean;
withDeleted?: boolean;
withExif?: boolean;
@ -830,6 +830,14 @@ export type SystemConfigFFmpegDto = {
transcode: TranscodePolicy;
twoPass: boolean;
};
export type SystemConfigThumbnailDto = {
colorspace: Colorspace;
previewFormat: ImageFormat;
previewSize: number;
quality: number;
thumbnailFormat: ImageFormat;
thumbnailSize: number;
};
export type JobSettingsDto = {
concurrency: number;
};
@ -922,12 +930,6 @@ export type SystemConfigStorageTemplateDto = {
export type SystemConfigThemeDto = {
customCss: string;
};
export type SystemConfigThumbnailDto = {
colorspace: Colorspace;
jpegSize: number;
quality: number;
webpSize: number;
};
export type SystemConfigTrashDto = {
days: number;
enabled: boolean;
@ -937,6 +939,7 @@ export type SystemConfigUserDto = {
};
export type SystemConfigDto = {
ffmpeg: SystemConfigFFmpegDto;
image: SystemConfigThumbnailDto;
job: SystemConfigJobDto;
library: SystemConfigLibraryDto;
logging: SystemConfigLoggingDto;
@ -949,7 +952,6 @@ export type SystemConfigDto = {
server: SystemConfigServerDto;
storageTemplate: SystemConfigStorageTemplateDto;
theme: SystemConfigThemeDto;
thumbnail: SystemConfigThumbnailDto;
trash: SystemConfigTrashDto;
user: SystemConfigUserDto;
};
@ -1562,7 +1564,7 @@ export function updateAsset({ id, updateAssetDto }: {
body: updateAssetDto
})));
}
export function searchAssets({ checksum, city, country, createdAfter, createdBefore, deviceAssetId, deviceId, encodedVideoPath, id, isArchived, isEncoded, isExternal, isFavorite, isMotion, isNotInAlbum, isOffline, isReadOnly, isVisible, lensModel, libraryId, make, model, order, originalFileName, originalPath, page, personIds, resizePath, size, state, takenAfter, takenBefore, trashedAfter, trashedBefore, $type, updatedAfter, updatedBefore, webpPath, withArchived, withDeleted, withExif, withPeople, withStacked }: {
export function searchAssets({ checksum, city, country, createdAfter, createdBefore, deviceAssetId, deviceId, encodedVideoPath, id, isArchived, isEncoded, isExternal, isFavorite, isMotion, isNotInAlbum, isOffline, isReadOnly, isVisible, lensModel, libraryId, make, model, order, originalFileName, originalPath, page, personIds, previewPath, size, state, takenAfter, takenBefore, thumbnailPath, trashedAfter, trashedBefore, $type, updatedAfter, updatedBefore, withArchived, withDeleted, withExif, withPeople, withStacked }: {
checksum?: string;
city?: string;
country?: string;
@ -1590,17 +1592,17 @@ export function searchAssets({ checksum, city, country, createdAfter, createdBef
originalPath?: string;
page?: number;
personIds?: string[];
resizePath?: string;
previewPath?: string;
size?: number;
state?: string;
takenAfter?: string;
takenBefore?: string;
thumbnailPath?: string;
trashedAfter?: string;
trashedBefore?: string;
$type?: AssetTypeEnum;
updatedAfter?: string;
updatedBefore?: string;
webpPath?: string;
withArchived?: boolean;
withDeleted?: boolean;
withExif?: boolean;
@ -1638,17 +1640,17 @@ export function searchAssets({ checksum, city, country, createdAfter, createdBef
originalPath,
page,
personIds,
resizePath,
previewPath,
size,
state,
takenAfter,
takenBefore,
thumbnailPath,
trashedAfter,
trashedBefore,
"type": $type,
updatedAfter,
updatedBefore,
webpPath,
withArchived,
withDeleted,
withExif,
@ -2785,8 +2787,8 @@ export enum AssetJobName {
TranscodeVideo = "transcode-video"
}
export enum ThumbnailFormat {
Jpeg = "JPEG",
Webp = "WEBP"
Jpeg = "jpeg",
Webp = "webp"
}
export enum TimeBucketSize {
Day = "DAY",
@ -2803,8 +2805,8 @@ export enum PathEntityType {
}
export enum PathType {
Original = "original",
JpegThumbnail = "jpeg_thumbnail",
WebpThumbnail = "webp_thumbnail",
Preview = "preview",
Thumbnail = "thumbnail",
EncodedVideo = "encoded_video",
Sidecar = "sidecar",
Face = "face",
@ -2886,6 +2888,14 @@ export enum TranscodePolicy {
Required = "required",
Disabled = "disabled"
}
export enum Colorspace {
Srgb = "srgb",
P3 = "p3"
}
export enum ImageFormat {
Jpeg = "jpeg",
Webp = "webp"
}
export enum LogLevel {
Verbose = "verbose",
Debug = "debug",
@ -2902,10 +2912,6 @@ export enum ModelType {
FacialRecognition = "facial-recognition",
Clip = "clip"
}
export enum Colorspace {
Srgb = "srgb",
P3 = "p3"
}
export enum MapTheme {
Light = "light",
Dark = "dark"

View File

@ -4,6 +4,7 @@ import { SystemConfigCore } from 'src/cores/system-config.core';
import { AssetEntity } from 'src/entities/asset.entity';
import { AssetPathType, PathType, PersonPathType } from 'src/entities/move.entity';
import { PersonEntity } from 'src/entities/person.entity';
import { ImageFormat } from 'src/entities/system-config.entity';
import { IAssetRepository } from 'src/interfaces/asset.interface';
import { ICryptoRepository } from 'src/interfaces/crypto.interface';
import { IMoveRepository } from 'src/interfaces/move.interface';
@ -34,7 +35,8 @@ export interface MoveRequest {
};
}
type GeneratedAssetPath = AssetPathType.JPEG_THUMBNAIL | AssetPathType.WEBP_THUMBNAIL | AssetPathType.ENCODED_VIDEO;
export type GeneratedImageType = AssetPathType.PREVIEW | AssetPathType.THUMBNAIL;
export type GeneratedAssetType = GeneratedImageType | AssetPathType.ENCODED_VIDEO;
let instance: StorageCore | null;
@ -94,12 +96,8 @@ export class StorageCore {
return StorageCore.getNestedPath(StorageFolder.THUMBNAILS, person.ownerId, `${person.id}.jpeg`);
}
static getLargeThumbnailPath(asset: AssetEntity) {
return StorageCore.getNestedPath(StorageFolder.THUMBNAILS, asset.ownerId, `${asset.id}.jpeg`);
}
static getSmallThumbnailPath(asset: AssetEntity) {
return StorageCore.getNestedPath(StorageFolder.THUMBNAILS, asset.ownerId, `${asset.id}.webp`);
static getImagePath(asset: AssetEntity, type: GeneratedImageType, format: ImageFormat) {
return StorageCore.getNestedPath(StorageFolder.THUMBNAILS, asset.ownerId, `${asset.id}-${type}.${format}`);
}
static getEncodedVideoPath(asset: AssetEntity) {
@ -122,35 +120,24 @@ export class StorageCore {
return path.startsWith(THUMBNAIL_DIR) || path.startsWith(ENCODED_VIDEO_DIR);
}
async moveAssetFile(asset: AssetEntity, pathType: GeneratedAssetPath) {
const { id: entityId, resizePath, webpPath, encodedVideoPath } = asset;
switch (pathType) {
case AssetPathType.JPEG_THUMBNAIL: {
async moveAssetImage(asset: AssetEntity, pathType: GeneratedImageType, format: ImageFormat) {
const { id: entityId, previewPath, thumbnailPath } = asset;
return this.moveFile({
entityId,
pathType,
oldPath: resizePath,
newPath: StorageCore.getLargeThumbnailPath(asset),
oldPath: pathType === AssetPathType.PREVIEW ? previewPath : thumbnailPath,
newPath: StorageCore.getImagePath(asset, AssetPathType.THUMBNAIL, format),
});
}
case AssetPathType.WEBP_THUMBNAIL: {
async moveAssetVideo(asset: AssetEntity) {
return this.moveFile({
entityId,
pathType,
oldPath: webpPath,
newPath: StorageCore.getSmallThumbnailPath(asset),
});
}
case AssetPathType.ENCODED_VIDEO: {
return this.moveFile({
entityId,
pathType,
oldPath: encodedVideoPath,
entityId: asset.id,
pathType: AssetPathType.ENCODED_VIDEO,
oldPath: asset.encodedVideoPath,
newPath: StorageCore.getEncodedVideoPath(asset),
});
}
}
}
async movePersonFile(person: PersonEntity, pathType: PersonPathType) {
const { id: entityId, thumbnailPath } = person;
@ -288,11 +275,11 @@ export class StorageCore {
case AssetPathType.ORIGINAL: {
return this.assetRepository.update({ id, originalPath: newPath });
}
case AssetPathType.JPEG_THUMBNAIL: {
return this.assetRepository.update({ id, resizePath: newPath });
case AssetPathType.PREVIEW: {
return this.assetRepository.update({ id, previewPath: newPath });
}
case AssetPathType.WEBP_THUMBNAIL: {
return this.assetRepository.update({ id, webpPath: newPath });
case AssetPathType.THUMBNAIL: {
return this.assetRepository.update({ id, thumbnailPath: newPath });
}
case AssetPathType.ENCODED_VIDEO: {
return this.assetRepository.update({ id, encodedVideoPath: newPath });

View File

@ -10,6 +10,7 @@ import {
AudioCodec,
CQMode,
Colorspace,
ImageFormat,
LogLevel,
SystemConfig,
SystemConfigEntity,
@ -112,9 +113,11 @@ export const defaults = Object.freeze<SystemConfig>({
hashVerificationEnabled: true,
template: '{{y}}/{{y}}-{{MM}}-{{dd}}/{{filename}}',
},
thumbnail: {
webpSize: 250,
jpegSize: 1440,
image: {
thumbnailFormat: ImageFormat.WEBP,
thumbnailSize: 250,
previewFormat: ImageFormat.JPEG,
previewSize: 1440,
quality: 80,
colorspace: Colorspace.P3,
},

View File

@ -82,7 +82,7 @@ export function mapAsset(entity: AssetEntity, options: AssetMapOptions = {}): As
type: entity.type,
thumbhash: entity.thumbhash?.toString('base64') ?? null,
localDateTime: entity.localDateTime,
resized: !!entity.resizePath,
resized: !!entity.previewPath,
duration: entity.duration ?? '0:00:00.00000',
livePhotoVideoId: entity.livePhotoVideoId,
hasMetadata: false,
@ -100,7 +100,7 @@ export function mapAsset(entity: AssetEntity, options: AssetMapOptions = {}): As
type: entity.type,
originalPath: entity.originalPath,
originalFileName: entity.originalFileName,
resized: !!entity.resizePath,
resized: !!entity.previewPath,
thumbhash: entity.thumbhash?.toString('base64') ?? null,
fileCreatedAt: entity.fileCreatedAt,
fileModifiedAt: entity.fileModifiedAt,

View File

@ -31,7 +31,7 @@ export class CheckExistingAssetsResponseDto {
export class CuratedLocationsResponseDto {
id!: string;
city!: string;
resizePath!: string;
previewPath!: string;
deviceAssetId!: string;
deviceId!: string;
}
@ -39,7 +39,7 @@ export class CuratedLocationsResponseDto {
export class CuratedObjectsResponseDto {
id!: string;
object!: string;
resizePath!: string;
previewPath!: string;
deviceAssetId!: string;
deviceId!: string;
}

View File

@ -2,6 +2,7 @@ import { ApiProperty } from '@nestjs/swagger';
import { Type } from 'class-transformer';
import { ArrayNotEmpty, IsArray, IsEnum, IsInt, IsNotEmpty, IsString, IsUUID, ValidateNested } from 'class-validator';
import { UploadFieldName } from 'src/dtos/asset.dto';
import { ImageFormat } from 'src/entities/system-config.entity';
import { Optional, ValidateBoolean, ValidateDate, ValidateUUID } from 'src/validation';
export class AssetBulkUploadCheckItem {
@ -112,22 +113,17 @@ export class CreateAssetDto {
[UploadFieldName.SIDECAR_DATA]?: any;
}
export enum GetAssetThumbnailFormatEnum {
JPEG = 'JPEG',
WEBP = 'WEBP',
}
export class GetAssetThumbnailDto {
@Optional()
@IsEnum(GetAssetThumbnailFormatEnum)
@IsEnum(ImageFormat)
@ApiProperty({
type: String,
enum: GetAssetThumbnailFormatEnum,
default: GetAssetThumbnailFormatEnum.WEBP,
enum: ImageFormat,
default: ImageFormat.WEBP,
required: false,
enumName: 'ThumbnailFormat',
})
format: GetAssetThumbnailFormatEnum = GetAssetThumbnailFormatEnum.WEBP;
format: ImageFormat = ImageFormat.WEBP;
}
export class SearchPropertiesDto {

View File

@ -163,12 +163,12 @@ export class MetadataSearchDto extends BaseSearchDto {
@IsString()
@IsNotEmpty()
@Optional()
resizePath?: string;
previewPath?: string;
@IsString()
@IsNotEmpty()
@Optional()
webpPath?: string;
thumbnailPath?: string;
@IsString()
@IsNotEmpty()

View File

@ -22,6 +22,7 @@ import {
AudioCodec,
CQMode,
Colorspace,
ImageFormat,
LogLevel,
SystemConfig,
ToneMapping,
@ -386,17 +387,25 @@ export class SystemConfigThemeDto {
}
class SystemConfigThumbnailDto {
@IsInt()
@Min(1)
@Type(() => Number)
@ApiProperty({ type: 'integer' })
webpSize!: number;
@IsEnum(ImageFormat)
@ApiProperty({ enumName: 'ImageFormat', enum: ImageFormat })
thumbnailFormat!: ImageFormat;
@IsInt()
@Min(1)
@Type(() => Number)
@ApiProperty({ type: 'integer' })
jpegSize!: number;
thumbnailSize!: number;
@IsEnum(ImageFormat)
@ApiProperty({ enumName: 'ImageFormat', enum: ImageFormat })
previewFormat!: ImageFormat;
@IsInt()
@Min(1)
@Type(() => Number)
@ApiProperty({ type: 'integer' })
previewSize!: number;
@IsInt()
@Min(1)
@ -483,7 +492,7 @@ export class SystemConfigDto implements SystemConfig {
@Type(() => SystemConfigThumbnailDto)
@ValidateNested()
@IsObject()
thumbnail!: SystemConfigThumbnailDto;
image!: SystemConfigThumbnailDto;
@Type(() => SystemConfigTrashDto)
@ValidateNested()

View File

@ -67,10 +67,10 @@ export class AssetEntity {
originalPath!: string;
@Column({ type: 'varchar', nullable: true })
resizePath!: string | null;
previewPath!: string | null;
@Column({ type: 'varchar', nullable: true, default: '' })
webpPath!: string | null;
thumbnailPath!: string | null;
@Column({ type: 'bytea', nullable: true })
thumbhash!: Buffer | null;

View File

@ -24,8 +24,8 @@ export class MoveEntity {
export enum AssetPathType {
ORIGINAL = 'original',
JPEG_THUMBNAIL = 'jpeg_thumbnail',
WEBP_THUMBNAIL = 'webp_thumbnail',
PREVIEW = 'preview',
THUMBNAIL = 'thumbnail',
ENCODED_VIDEO = 'encoded_video',
SIDECAR = 'sidecar',
}

View File

@ -165,6 +165,11 @@ export enum Colorspace {
P3 = 'p3',
}
export enum ImageFormat {
JPEG = 'jpeg',
WEBP = 'webp',
}
export enum LogLevel {
VERBOSE = 'verbose',
DEBUG = 'debug',
@ -249,9 +254,11 @@ export interface SystemConfig {
hashVerificationEnabled: boolean;
template: string;
};
thumbnail: {
webpSize: number;
jpegSize: number;
image: {
thumbnailFormat: ImageFormat;
thumbnailSize: number;
previewFormat: ImageFormat;
previewSize: number;
quality: number;
colorspace: Colorspace;
};

View File

@ -33,8 +33,8 @@ export enum JobName {
// thumbnails
QUEUE_GENERATE_THUMBNAILS = 'queue-generate-thumbnails',
GENERATE_JPEG_THUMBNAIL = 'generate-jpeg-thumbnail',
GENERATE_WEBP_THUMBNAIL = 'generate-webp-thumbnail',
GENERATE_THUMBNAIL = 'generate-thumbnail',
GENERATE_PREVIEW = 'generate-preview',
GENERATE_THUMBHASH_THUMBNAIL = 'generate-thumbhash-thumbnail',
GENERATE_PERSON_THUMBNAIL = 'generate-person-thumbnail',
@ -160,8 +160,8 @@ export type JobItem =
// Thumbnails
| { name: JobName.QUEUE_GENERATE_THUMBNAILS; data: IBaseJob }
| { name: JobName.GENERATE_JPEG_THUMBNAIL; data: IEntityJob }
| { name: JobName.GENERATE_WEBP_THUMBNAIL; data: IEntityJob }
| { name: JobName.GENERATE_THUMBNAIL; data: IEntityJob }
| { name: JobName.GENERATE_PREVIEW; data: IEntityJob }
| { name: JobName.GENERATE_THUMBHASH_THUMBNAIL; data: IEntityJob }
// User

View File

@ -1,11 +1,11 @@
import { Writable } from 'node:stream';
import { TranscodeTarget, VideoCodec } from 'src/entities/system-config.entity';
import { ImageFormat, TranscodeTarget, VideoCodec } from 'src/entities/system-config.entity';
export const IMediaRepository = 'IMediaRepository';
export interface ResizeOptions {
size: number;
format: 'webp' | 'jpeg';
format: ImageFormat;
colorspace: string;
quality: number;
}

View File

@ -117,8 +117,8 @@ export interface SearchPathOptions {
encodedVideoPath?: string;
originalFileName?: string;
originalPath?: string;
resizePath?: string;
webpPath?: string;
previewPath?: string;
thumbnailPath?: string;
}
export interface SearchExifOptions {

View File

@ -0,0 +1,31 @@
import { MigrationInterface, QueryRunner } from 'typeorm';
export class RenameWebpJpegPaths1711257900274 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.renameColumn('assets', 'webpPath', 'thumbnailPath');
await queryRunner.renameColumn('assets', 'resizePath', 'previewPath');
await queryRunner.query(`
UPDATE system_config
SET key = 'image.previewSize'
WHERE key = 'thumbnail.jpegSize'`);
await queryRunner.query(
`UPDATE system_config
SET key = 'image.thumbnailSize'
WHERE key = 'thumbnail.webpSize'`,
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.renameColumn('assets', 'thumbnailPath', 'webpPath');
await queryRunner.renameColumn('assets', 'previewPath', 'resizePath');
await queryRunner.query(`
UPDATE system_config
SET key = 'thumbnail.jpegSize'
WHERE key = 'image.previewSize'`);
await queryRunner.query(
`UPDATE system_config
SET key = 'thumbnail.webpSize'
WHERE key = 'image.thumbnailSize'`,
);
}
}

View File

@ -9,8 +9,8 @@ SELECT
"AssetEntity"."deviceId" AS "AssetEntity_deviceId",
"AssetEntity"."type" AS "AssetEntity_type",
"AssetEntity"."originalPath" AS "AssetEntity_originalPath",
"AssetEntity"."resizePath" AS "AssetEntity_resizePath",
"AssetEntity"."webpPath" AS "AssetEntity_webpPath",
"AssetEntity"."previewPath" AS "AssetEntity_previewPath",
"AssetEntity"."thumbnailPath" AS "AssetEntity_thumbnailPath",
"AssetEntity"."thumbhash" AS "AssetEntity_thumbhash",
"AssetEntity"."encodedVideoPath" AS "AssetEntity_encodedVideoPath",
"AssetEntity"."createdAt" AS "AssetEntity_createdAt",
@ -45,8 +45,8 @@ SELECT
"AssetEntity"."deviceId" AS "AssetEntity_deviceId",
"AssetEntity"."type" AS "AssetEntity_type",
"AssetEntity"."originalPath" AS "AssetEntity_originalPath",
"AssetEntity"."resizePath" AS "AssetEntity_resizePath",
"AssetEntity"."webpPath" AS "AssetEntity_webpPath",
"AssetEntity"."previewPath" AS "AssetEntity_previewPath",
"AssetEntity"."thumbnailPath" AS "AssetEntity_thumbnailPath",
"AssetEntity"."thumbhash" AS "AssetEntity_thumbhash",
"AssetEntity"."encodedVideoPath" AS "AssetEntity_encodedVideoPath",
"AssetEntity"."createdAt" AS "AssetEntity_createdAt",
@ -130,8 +130,8 @@ SELECT
"bd93d5747511a4dad4923546c51365bf1a803774"."deviceId" AS "bd93d5747511a4dad4923546c51365bf1a803774_deviceId",
"bd93d5747511a4dad4923546c51365bf1a803774"."type" AS "bd93d5747511a4dad4923546c51365bf1a803774_type",
"bd93d5747511a4dad4923546c51365bf1a803774"."originalPath" AS "bd93d5747511a4dad4923546c51365bf1a803774_originalPath",
"bd93d5747511a4dad4923546c51365bf1a803774"."resizePath" AS "bd93d5747511a4dad4923546c51365bf1a803774_resizePath",
"bd93d5747511a4dad4923546c51365bf1a803774"."webpPath" AS "bd93d5747511a4dad4923546c51365bf1a803774_webpPath",
"bd93d5747511a4dad4923546c51365bf1a803774"."previewPath" AS "bd93d5747511a4dad4923546c51365bf1a803774_previewPath",
"bd93d5747511a4dad4923546c51365bf1a803774"."thumbnailPath" AS "bd93d5747511a4dad4923546c51365bf1a803774_thumbnailPath",
"bd93d5747511a4dad4923546c51365bf1a803774"."thumbhash" AS "bd93d5747511a4dad4923546c51365bf1a803774_thumbhash",
"bd93d5747511a4dad4923546c51365bf1a803774"."encodedVideoPath" AS "bd93d5747511a4dad4923546c51365bf1a803774_encodedVideoPath",
"bd93d5747511a4dad4923546c51365bf1a803774"."createdAt" AS "bd93d5747511a4dad4923546c51365bf1a803774_createdAt",
@ -183,8 +183,8 @@ FROM
"AssetEntity"."deviceId" AS "AssetEntity_deviceId",
"AssetEntity"."type" AS "AssetEntity_type",
"AssetEntity"."originalPath" AS "AssetEntity_originalPath",
"AssetEntity"."resizePath" AS "AssetEntity_resizePath",
"AssetEntity"."webpPath" AS "AssetEntity_webpPath",
"AssetEntity"."previewPath" AS "AssetEntity_previewPath",
"AssetEntity"."thumbnailPath" AS "AssetEntity_thumbnailPath",
"AssetEntity"."thumbhash" AS "AssetEntity_thumbhash",
"AssetEntity"."encodedVideoPath" AS "AssetEntity_encodedVideoPath",
"AssetEntity"."createdAt" AS "AssetEntity_createdAt",
@ -280,8 +280,8 @@ SELECT
"AssetEntity"."deviceId" AS "AssetEntity_deviceId",
"AssetEntity"."type" AS "AssetEntity_type",
"AssetEntity"."originalPath" AS "AssetEntity_originalPath",
"AssetEntity"."resizePath" AS "AssetEntity_resizePath",
"AssetEntity"."webpPath" AS "AssetEntity_webpPath",
"AssetEntity"."previewPath" AS "AssetEntity_previewPath",
"AssetEntity"."thumbnailPath" AS "AssetEntity_thumbnailPath",
"AssetEntity"."thumbhash" AS "AssetEntity_thumbhash",
"AssetEntity"."encodedVideoPath" AS "AssetEntity_encodedVideoPath",
"AssetEntity"."createdAt" AS "AssetEntity_createdAt",
@ -326,8 +326,8 @@ SELECT
"AssetEntity"."deviceId" AS "AssetEntity_deviceId",
"AssetEntity"."type" AS "AssetEntity_type",
"AssetEntity"."originalPath" AS "AssetEntity_originalPath",
"AssetEntity"."resizePath" AS "AssetEntity_resizePath",
"AssetEntity"."webpPath" AS "AssetEntity_webpPath",
"AssetEntity"."previewPath" AS "AssetEntity_previewPath",
"AssetEntity"."thumbnailPath" AS "AssetEntity_thumbnailPath",
"AssetEntity"."thumbhash" AS "AssetEntity_thumbhash",
"AssetEntity"."encodedVideoPath" AS "AssetEntity_encodedVideoPath",
"AssetEntity"."createdAt" AS "AssetEntity_createdAt",
@ -370,8 +370,8 @@ SELECT
"AssetEntity"."deviceId" AS "AssetEntity_deviceId",
"AssetEntity"."type" AS "AssetEntity_type",
"AssetEntity"."originalPath" AS "AssetEntity_originalPath",
"AssetEntity"."resizePath" AS "AssetEntity_resizePath",
"AssetEntity"."webpPath" AS "AssetEntity_webpPath",
"AssetEntity"."previewPath" AS "AssetEntity_previewPath",
"AssetEntity"."thumbnailPath" AS "AssetEntity_thumbnailPath",
"AssetEntity"."thumbhash" AS "AssetEntity_thumbhash",
"AssetEntity"."encodedVideoPath" AS "AssetEntity_encodedVideoPath",
"AssetEntity"."createdAt" AS "AssetEntity_createdAt",
@ -459,8 +459,8 @@ SELECT
"asset"."deviceId" AS "asset_deviceId",
"asset"."type" AS "asset_type",
"asset"."originalPath" AS "asset_originalPath",
"asset"."resizePath" AS "asset_resizePath",
"asset"."webpPath" AS "asset_webpPath",
"asset"."previewPath" AS "asset_previewPath",
"asset"."thumbnailPath" AS "asset_thumbnailPath",
"asset"."thumbhash" AS "asset_thumbhash",
"asset"."encodedVideoPath" AS "asset_encodedVideoPath",
"asset"."createdAt" AS "asset_createdAt",
@ -518,8 +518,8 @@ SELECT
"stackedAssets"."deviceId" AS "stackedAssets_deviceId",
"stackedAssets"."type" AS "stackedAssets_type",
"stackedAssets"."originalPath" AS "stackedAssets_originalPath",
"stackedAssets"."resizePath" AS "stackedAssets_resizePath",
"stackedAssets"."webpPath" AS "stackedAssets_webpPath",
"stackedAssets"."previewPath" AS "stackedAssets_previewPath",
"stackedAssets"."thumbnailPath" AS "stackedAssets_thumbnailPath",
"stackedAssets"."thumbhash" AS "stackedAssets_thumbhash",
"stackedAssets"."encodedVideoPath" AS "stackedAssets_encodedVideoPath",
"stackedAssets"."createdAt" AS "stackedAssets_createdAt",

View File

@ -152,8 +152,8 @@ FROM
"AssetFaceEntity__AssetFaceEntity_asset"."deviceId" AS "AssetFaceEntity__AssetFaceEntity_asset_deviceId",
"AssetFaceEntity__AssetFaceEntity_asset"."type" AS "AssetFaceEntity__AssetFaceEntity_asset_type",
"AssetFaceEntity__AssetFaceEntity_asset"."originalPath" AS "AssetFaceEntity__AssetFaceEntity_asset_originalPath",
"AssetFaceEntity__AssetFaceEntity_asset"."resizePath" AS "AssetFaceEntity__AssetFaceEntity_asset_resizePath",
"AssetFaceEntity__AssetFaceEntity_asset"."webpPath" AS "AssetFaceEntity__AssetFaceEntity_asset_webpPath",
"AssetFaceEntity__AssetFaceEntity_asset"."previewPath" AS "AssetFaceEntity__AssetFaceEntity_asset_previewPath",
"AssetFaceEntity__AssetFaceEntity_asset"."thumbnailPath" AS "AssetFaceEntity__AssetFaceEntity_asset_thumbnailPath",
"AssetFaceEntity__AssetFaceEntity_asset"."thumbhash" AS "AssetFaceEntity__AssetFaceEntity_asset_thumbhash",
"AssetFaceEntity__AssetFaceEntity_asset"."encodedVideoPath" AS "AssetFaceEntity__AssetFaceEntity_asset_encodedVideoPath",
"AssetFaceEntity__AssetFaceEntity_asset"."createdAt" AS "AssetFaceEntity__AssetFaceEntity_asset_createdAt",
@ -250,8 +250,8 @@ FROM
"AssetEntity"."deviceId" AS "AssetEntity_deviceId",
"AssetEntity"."type" AS "AssetEntity_type",
"AssetEntity"."originalPath" AS "AssetEntity_originalPath",
"AssetEntity"."resizePath" AS "AssetEntity_resizePath",
"AssetEntity"."webpPath" AS "AssetEntity_webpPath",
"AssetEntity"."previewPath" AS "AssetEntity_previewPath",
"AssetEntity"."thumbnailPath" AS "AssetEntity_thumbnailPath",
"AssetEntity"."thumbhash" AS "AssetEntity_thumbhash",
"AssetEntity"."encodedVideoPath" AS "AssetEntity_encodedVideoPath",
"AssetEntity"."createdAt" AS "AssetEntity_createdAt",
@ -380,8 +380,8 @@ SELECT
"AssetFaceEntity__AssetFaceEntity_asset"."deviceId" AS "AssetFaceEntity__AssetFaceEntity_asset_deviceId",
"AssetFaceEntity__AssetFaceEntity_asset"."type" AS "AssetFaceEntity__AssetFaceEntity_asset_type",
"AssetFaceEntity__AssetFaceEntity_asset"."originalPath" AS "AssetFaceEntity__AssetFaceEntity_asset_originalPath",
"AssetFaceEntity__AssetFaceEntity_asset"."resizePath" AS "AssetFaceEntity__AssetFaceEntity_asset_resizePath",
"AssetFaceEntity__AssetFaceEntity_asset"."webpPath" AS "AssetFaceEntity__AssetFaceEntity_asset_webpPath",
"AssetFaceEntity__AssetFaceEntity_asset"."previewPath" AS "AssetFaceEntity__AssetFaceEntity_asset_previewPath",
"AssetFaceEntity__AssetFaceEntity_asset"."thumbnailPath" AS "AssetFaceEntity__AssetFaceEntity_asset_thumbnailPath",
"AssetFaceEntity__AssetFaceEntity_asset"."thumbhash" AS "AssetFaceEntity__AssetFaceEntity_asset_thumbhash",
"AssetFaceEntity__AssetFaceEntity_asset"."encodedVideoPath" AS "AssetFaceEntity__AssetFaceEntity_asset_encodedVideoPath",
"AssetFaceEntity__AssetFaceEntity_asset"."createdAt" AS "AssetFaceEntity__AssetFaceEntity_asset_createdAt",

View File

@ -14,8 +14,8 @@ FROM
"asset"."deviceId" AS "asset_deviceId",
"asset"."type" AS "asset_type",
"asset"."originalPath" AS "asset_originalPath",
"asset"."resizePath" AS "asset_resizePath",
"asset"."webpPath" AS "asset_webpPath",
"asset"."previewPath" AS "asset_previewPath",
"asset"."thumbnailPath" AS "asset_thumbnailPath",
"asset"."thumbhash" AS "asset_thumbhash",
"asset"."encodedVideoPath" AS "asset_encodedVideoPath",
"asset"."createdAt" AS "asset_createdAt",
@ -45,8 +45,8 @@ FROM
"stackedAssets"."deviceId" AS "stackedAssets_deviceId",
"stackedAssets"."type" AS "stackedAssets_type",
"stackedAssets"."originalPath" AS "stackedAssets_originalPath",
"stackedAssets"."resizePath" AS "stackedAssets_resizePath",
"stackedAssets"."webpPath" AS "stackedAssets_webpPath",
"stackedAssets"."previewPath" AS "stackedAssets_previewPath",
"stackedAssets"."thumbnailPath" AS "stackedAssets_thumbnailPath",
"stackedAssets"."thumbhash" AS "stackedAssets_thumbhash",
"stackedAssets"."encodedVideoPath" AS "stackedAssets_encodedVideoPath",
"stackedAssets"."createdAt" AS "stackedAssets_createdAt",
@ -110,8 +110,8 @@ SELECT
"asset"."deviceId" AS "asset_deviceId",
"asset"."type" AS "asset_type",
"asset"."originalPath" AS "asset_originalPath",
"asset"."resizePath" AS "asset_resizePath",
"asset"."webpPath" AS "asset_webpPath",
"asset"."previewPath" AS "asset_previewPath",
"asset"."thumbnailPath" AS "asset_thumbnailPath",
"asset"."thumbhash" AS "asset_thumbhash",
"asset"."encodedVideoPath" AS "asset_encodedVideoPath",
"asset"."createdAt" AS "asset_createdAt",
@ -141,8 +141,8 @@ SELECT
"stackedAssets"."deviceId" AS "stackedAssets_deviceId",
"stackedAssets"."type" AS "stackedAssets_type",
"stackedAssets"."originalPath" AS "stackedAssets_originalPath",
"stackedAssets"."resizePath" AS "stackedAssets_resizePath",
"stackedAssets"."webpPath" AS "stackedAssets_webpPath",
"stackedAssets"."previewPath" AS "stackedAssets_previewPath",
"stackedAssets"."thumbnailPath" AS "stackedAssets_thumbnailPath",
"stackedAssets"."thumbhash" AS "stackedAssets_thumbhash",
"stackedAssets"."encodedVideoPath" AS "stackedAssets_encodedVideoPath",
"stackedAssets"."createdAt" AS "stackedAssets_createdAt",
@ -320,8 +320,8 @@ SELECT
"asset"."deviceId" AS "asset_deviceId",
"asset"."type" AS "asset_type",
"asset"."originalPath" AS "asset_originalPath",
"asset"."resizePath" AS "asset_resizePath",
"asset"."webpPath" AS "asset_webpPath",
"asset"."previewPath" AS "asset_previewPath",
"asset"."thumbnailPath" AS "asset_thumbnailPath",
"asset"."thumbhash" AS "asset_thumbhash",
"asset"."encodedVideoPath" AS "asset_encodedVideoPath",
"asset"."createdAt" AS "asset_createdAt",

View File

@ -28,8 +28,8 @@ FROM
"SharedLinkEntity__SharedLinkEntity_assets"."deviceId" AS "SharedLinkEntity__SharedLinkEntity_assets_deviceId",
"SharedLinkEntity__SharedLinkEntity_assets"."type" AS "SharedLinkEntity__SharedLinkEntity_assets_type",
"SharedLinkEntity__SharedLinkEntity_assets"."originalPath" AS "SharedLinkEntity__SharedLinkEntity_assets_originalPath",
"SharedLinkEntity__SharedLinkEntity_assets"."resizePath" AS "SharedLinkEntity__SharedLinkEntity_assets_resizePath",
"SharedLinkEntity__SharedLinkEntity_assets"."webpPath" AS "SharedLinkEntity__SharedLinkEntity_assets_webpPath",
"SharedLinkEntity__SharedLinkEntity_assets"."previewPath" AS "SharedLinkEntity__SharedLinkEntity_assets_previewPath",
"SharedLinkEntity__SharedLinkEntity_assets"."thumbnailPath" AS "SharedLinkEntity__SharedLinkEntity_assets_thumbnailPath",
"SharedLinkEntity__SharedLinkEntity_assets"."thumbhash" AS "SharedLinkEntity__SharedLinkEntity_assets_thumbhash",
"SharedLinkEntity__SharedLinkEntity_assets"."encodedVideoPath" AS "SharedLinkEntity__SharedLinkEntity_assets_encodedVideoPath",
"SharedLinkEntity__SharedLinkEntity_assets"."createdAt" AS "SharedLinkEntity__SharedLinkEntity_assets_createdAt",
@ -95,8 +95,8 @@ FROM
"4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."deviceId" AS "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6_deviceId",
"4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."type" AS "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6_type",
"4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."originalPath" AS "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6_originalPath",
"4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."resizePath" AS "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6_resizePath",
"4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."webpPath" AS "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6_webpPath",
"4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."previewPath" AS "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6_previewPath",
"4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."thumbnailPath" AS "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6_thumbnailPath",
"4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."thumbhash" AS "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6_thumbhash",
"4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."encodedVideoPath" AS "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6_encodedVideoPath",
"4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."createdAt" AS "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6_createdAt",
@ -218,8 +218,8 @@ SELECT
"SharedLinkEntity__SharedLinkEntity_assets"."deviceId" AS "SharedLinkEntity__SharedLinkEntity_assets_deviceId",
"SharedLinkEntity__SharedLinkEntity_assets"."type" AS "SharedLinkEntity__SharedLinkEntity_assets_type",
"SharedLinkEntity__SharedLinkEntity_assets"."originalPath" AS "SharedLinkEntity__SharedLinkEntity_assets_originalPath",
"SharedLinkEntity__SharedLinkEntity_assets"."resizePath" AS "SharedLinkEntity__SharedLinkEntity_assets_resizePath",
"SharedLinkEntity__SharedLinkEntity_assets"."webpPath" AS "SharedLinkEntity__SharedLinkEntity_assets_webpPath",
"SharedLinkEntity__SharedLinkEntity_assets"."previewPath" AS "SharedLinkEntity__SharedLinkEntity_assets_previewPath",
"SharedLinkEntity__SharedLinkEntity_assets"."thumbnailPath" AS "SharedLinkEntity__SharedLinkEntity_assets_thumbnailPath",
"SharedLinkEntity__SharedLinkEntity_assets"."thumbhash" AS "SharedLinkEntity__SharedLinkEntity_assets_thumbhash",
"SharedLinkEntity__SharedLinkEntity_assets"."encodedVideoPath" AS "SharedLinkEntity__SharedLinkEntity_assets_encodedVideoPath",
"SharedLinkEntity__SharedLinkEntity_assets"."createdAt" AS "SharedLinkEntity__SharedLinkEntity_assets_createdAt",

View File

@ -66,7 +66,7 @@ export class AssetRepositoryV1 implements IAssetRepositoryV1 {
getDetectedObjectsByUserId(userId: string): Promise<CuratedObjectsResponseDto[]> {
return this.assetRepository.query(
`
SELECT DISTINCT ON (unnest(si.objects)) a.id, unnest(si.objects) as "object", a."resizePath", a."deviceAssetId", a."deviceId"
SELECT DISTINCT ON (unnest(si.objects)) a.id, unnest(si.objects) as "object", a."previewPath", a."deviceAssetId", a."deviceId"
FROM assets a
LEFT JOIN smart_info si ON a.id = si."assetId"
WHERE a."ownerId" = $1
@ -80,7 +80,7 @@ export class AssetRepositoryV1 implements IAssetRepositoryV1 {
getLocationsByUserId(userId: string): Promise<CuratedLocationsResponseDto[]> {
return this.assetRepository.query(
`
SELECT DISTINCT ON (e.city) a.id, e.city, a."resizePath", a."deviceAssetId", a."deviceId"
SELECT DISTINCT ON (e.city) a.id, e.city, a."previewPath", a."deviceAssetId", a."deviceId"
FROM assets a
LEFT JOIN exif e ON a.id = e."assetId"
WHERE a."ownerId" = $1

View File

@ -83,7 +83,7 @@ export class AssetRepository implements IAssetRepository {
`entity.ownerId IN (:...ownerIds)
AND entity.isVisible = true
AND entity.isArchived = false
AND entity.resizePath IS NOT NULL
AND entity.previewPath IS NOT NULL
AND EXTRACT(DAY FROM entity.localDateTime AT TIME ZONE 'UTC') = :day
AND EXTRACT(MONTH FROM entity.localDateTime AT TIME ZONE 'UTC') = :month`,
{
@ -302,10 +302,10 @@ export class AssetRepository implements IAssetRepository {
switch (property) {
case WithoutProperty.THUMBNAIL: {
where = [
{ resizePath: IsNull(), isVisible: true },
{ resizePath: '', isVisible: true },
{ webpPath: IsNull(), isVisible: true },
{ webpPath: '', isVisible: true },
{ previewPath: IsNull(), isVisible: true },
{ previewPath: '', isVisible: true },
{ thumbnailPath: IsNull(), isVisible: true },
{ thumbnailPath: '', isVisible: true },
{ thumbhash: IsNull(), isVisible: true },
];
break;
@ -339,7 +339,7 @@ export class AssetRepository implements IAssetRepository {
};
where = {
isVisible: true,
resizePath: Not(IsNull()),
previewPath: Not(IsNull()),
smartSearch: {
embedding: IsNull(),
},
@ -352,7 +352,7 @@ export class AssetRepository implements IAssetRepository {
smartInfo: true,
};
where = {
resizePath: Not(IsNull()),
previewPath: Not(IsNull()),
isVisible: true,
smartInfo: {
tags: IsNull(),
@ -367,7 +367,7 @@ export class AssetRepository implements IAssetRepository {
jobStatus: true,
};
where = {
resizePath: Not(IsNull()),
previewPath: Not(IsNull()),
isVisible: true,
faces: {
assetId: IsNull(),
@ -385,7 +385,7 @@ export class AssetRepository implements IAssetRepository {
faces: true,
};
where = {
resizePath: Not(IsNull()),
previewPath: Not(IsNull()),
isVisible: true,
faces: {
assetId: Not(IsNull()),

View File

@ -35,8 +35,8 @@ export const JOBS_TO_QUEUE: Record<JobName, QueueName> = {
// thumbnails
[JobName.QUEUE_GENERATE_THUMBNAILS]: QueueName.THUMBNAIL_GENERATION,
[JobName.GENERATE_JPEG_THUMBNAIL]: QueueName.THUMBNAIL_GENERATION,
[JobName.GENERATE_WEBP_THUMBNAIL]: QueueName.THUMBNAIL_GENERATION,
[JobName.GENERATE_THUMBNAIL]: QueueName.THUMBNAIL_GENERATION,
[JobName.GENERATE_PREVIEW]: QueueName.THUMBNAIL_GENERATION,
[JobName.GENERATE_THUMBHASH_THUMBNAIL]: QueueName.THUMBNAIL_GENERATION,
[JobName.GENERATE_PERSON_THUMBNAIL]: QueueName.THUMBNAIL_GENERATION,

View File

@ -44,13 +44,13 @@ const _getAsset_1 = () => {
asset_1.deviceId = 'device_id_1';
asset_1.type = AssetType.VIDEO;
asset_1.originalPath = 'fake_path/asset_1.jpeg';
asset_1.resizePath = '';
asset_1.previewPath = '';
asset_1.fileModifiedAt = new Date('2022-06-19T23:41:36.910Z');
asset_1.fileCreatedAt = new Date('2022-06-19T23:41:36.910Z');
asset_1.updatedAt = new Date('2022-06-19T23:41:36.910Z');
asset_1.isFavorite = false;
asset_1.isArchived = false;
asset_1.webpPath = '';
asset_1.thumbnailPath = '';
asset_1.encodedVideoPath = '';
asset_1.duration = '0:00:00.000000';
asset_1.exifInfo = new ExifEntity();

View File

@ -22,12 +22,12 @@ import {
CheckExistingAssetsDto,
CreateAssetDto,
GetAssetThumbnailDto,
GetAssetThumbnailFormatEnum,
ServeFileDto,
} from 'src/dtos/asset-v1.dto';
import { AuthDto } from 'src/dtos/auth.dto';
import { ASSET_CHECKSUM_CONSTRAINT, AssetEntity, AssetType } from 'src/entities/asset.entity';
import { LibraryType } from 'src/entities/library.entity';
import { ImageFormat } from 'src/entities/system-config.entity';
import { IAccessRepository } from 'src/interfaces/access.interface';
import { IAssetRepositoryV1 } from 'src/interfaces/asset-v1.interface';
import { IAssetRepository } from 'src/interfaces/asset.interface';
@ -244,19 +244,19 @@ export class AssetServiceV1 {
};
}
private getThumbnailPath(asset: AssetEntity, format: GetAssetThumbnailFormatEnum) {
private getThumbnailPath(asset: AssetEntity, format: ImageFormat) {
switch (format) {
case GetAssetThumbnailFormatEnum.WEBP: {
if (asset.webpPath) {
return asset.webpPath;
case ImageFormat.WEBP: {
if (asset.thumbnailPath) {
return asset.thumbnailPath;
}
this.logger.warn(`WebP thumbnail requested but not found for asset ${asset.id}, falling back to JPEG`);
}
case GetAssetThumbnailFormatEnum.JPEG: {
if (!asset.resizePath) {
case ImageFormat.JPEG: {
if (!asset.previewPath) {
throw new NotFoundException(`No thumbnail found for asset ${asset.id}`);
}
return asset.resizePath;
return asset.previewPath;
}
}
}
@ -268,12 +268,12 @@ export class AssetServiceV1 {
* Serve file viewer on the web
*/
if (dto.isWeb && mimeType != 'image/gif') {
if (!asset.resizePath) {
if (!asset.previewPath) {
this.logger.error('Error serving IMAGE asset for web');
throw new InternalServerErrorException(`Failed to serve image asset for web`, 'ServeFile');
}
return asset.resizePath;
return asset.previewPath;
}
/**
@ -283,15 +283,15 @@ export class AssetServiceV1 {
return asset.originalPath;
}
if (asset.webpPath && asset.webpPath.length > 0) {
return asset.webpPath;
if (asset.thumbnailPath && asset.thumbnailPath.length > 0) {
return asset.thumbnailPath;
}
if (!asset.resizePath) {
throw new Error('resizePath not set');
if (!asset.previewPath) {
throw new Error('previewPath not set');
}
return asset.resizePath;
return asset.previewPath;
}
private async getLibraryId(auth: AuthDto, libraryId?: string) {

View File

@ -777,8 +777,8 @@ describe(AssetService.name, () => {
name: JobName.DELETE_FILES,
data: {
files: [
assetWithFace.webpPath,
assetWithFace.resizePath,
assetWithFace.thumbnailPath,
assetWithFace.previewPath,
assetWithFace.encodedVideoPath,
assetWithFace.sidecarPath,
assetWithFace.originalPath,
@ -861,8 +861,8 @@ describe(AssetService.name, () => {
name: JobName.DELETE_FILES,
data: {
files: [
assetStub.external.webpPath,
assetStub.external.resizePath,
assetStub.external.thumbnailPath,
assetStub.external.previewPath,
assetStub.external.encodedVideoPath,
assetStub.external.sidecarPath,
],
@ -944,9 +944,7 @@ describe(AssetService.name, () => {
it('should run the refresh thumbnails job', async () => {
accessMock.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset-1']));
await sut.run(authStub.admin, { assetIds: ['asset-1'], name: AssetJobName.REGENERATE_THUMBNAIL }),
expect(jobMock.queueAll).toHaveBeenCalledWith([
{ name: JobName.GENERATE_JPEG_THUMBNAIL, data: { id: 'asset-1' } },
]);
expect(jobMock.queueAll).toHaveBeenCalledWith([{ name: JobName.GENERATE_THUMBNAIL, data: { id: 'asset-1' } }]);
});
it('should run the transcode video', async () => {

View File

@ -461,7 +461,7 @@ export class AssetService {
await this.jobRepository.queue({ name: JobName.ASSET_DELETION, data: { id: asset.livePhotoVideoId } });
}
const files = [asset.webpPath, asset.resizePath, asset.encodedVideoPath, asset.sidecarPath];
const files = [asset.thumbnailPath, asset.previewPath, asset.encodedVideoPath, asset.sidecarPath];
if (!fromExternal) {
files.push(asset.originalPath);
}
@ -534,7 +534,7 @@ export class AssetService {
}
case AssetJobName.REGENERATE_THUMBNAIL: {
jobs.push({ name: JobName.GENERATE_JPEG_THUMBNAIL, data: { id } });
jobs.push({ name: JobName.GENERATE_THUMBNAIL, data: { id } });
break;
}

View File

@ -95,13 +95,13 @@ export class AuditService {
break;
}
case AssetPathType.JPEG_THUMBNAIL: {
await this.assetRepository.update({ id, resizePath: pathValue });
case AssetPathType.PREVIEW: {
await this.assetRepository.update({ id, previewPath: pathValue });
break;
}
case AssetPathType.WEBP_THUMBNAIL: {
await this.assetRepository.update({ id, webpPath: pathValue });
case AssetPathType.THUMBNAIL: {
await this.assetRepository.update({ id, thumbnailPath: pathValue });
break;
}
@ -174,8 +174,8 @@ export class AuditService {
const orphans: FileReportItemDto[] = [];
for await (const assets of pagination) {
assetCount += assets.length;
for (const { id, originalPath, resizePath, encodedVideoPath, webpPath, isExternal, checksum } of assets) {
for (const file of [originalPath, resizePath, encodedVideoPath, webpPath]) {
for (const { id, originalPath, previewPath, encodedVideoPath, thumbnailPath, isExternal, checksum } of assets) {
for (const file of [originalPath, previewPath, encodedVideoPath, thumbnailPath]) {
track(file);
}
@ -191,14 +191,14 @@ export class AuditService {
) {
orphans.push({ ...entity, pathType: AssetPathType.ORIGINAL, pathValue: originalPath });
}
if (resizePath && !hasFile(thumbFiles, resizePath)) {
orphans.push({ ...entity, pathType: AssetPathType.JPEG_THUMBNAIL, pathValue: resizePath });
if (previewPath && !hasFile(thumbFiles, previewPath)) {
orphans.push({ ...entity, pathType: AssetPathType.PREVIEW, pathValue: previewPath });
}
if (webpPath && !hasFile(thumbFiles, webpPath)) {
orphans.push({ ...entity, pathType: AssetPathType.WEBP_THUMBNAIL, pathValue: webpPath });
if (thumbnailPath && !hasFile(thumbFiles, thumbnailPath)) {
orphans.push({ ...entity, pathType: AssetPathType.THUMBNAIL, pathValue: thumbnailPath });
}
if (encodedVideoPath && !hasFile(videoFiles, encodedVideoPath)) {
orphans.push({ ...entity, pathType: AssetPathType.WEBP_THUMBNAIL, pathValue: encodedVideoPath });
orphans.push({ ...entity, pathType: AssetPathType.THUMBNAIL, pathValue: encodedVideoPath });
}
}
}

View File

@ -275,7 +275,7 @@ describe(JobService.name, () => {
},
{
item: { name: JobName.STORAGE_TEMPLATE_MIGRATION_SINGLE, data: { id: 'asset-1', source: 'upload' } },
jobs: [JobName.GENERATE_JPEG_THUMBNAIL],
jobs: [JobName.GENERATE_THUMBNAIL],
},
{
item: { name: JobName.STORAGE_TEMPLATE_MIGRATION_SINGLE, data: { id: 'asset-1' } },
@ -286,13 +286,13 @@ describe(JobService.name, () => {
jobs: [],
},
{
item: { name: JobName.GENERATE_JPEG_THUMBNAIL, data: { id: 'asset-1' } },
jobs: [JobName.GENERATE_WEBP_THUMBNAIL, JobName.GENERATE_THUMBHASH_THUMBNAIL],
item: { name: JobName.GENERATE_THUMBNAIL, data: { id: 'asset-1' } },
jobs: [JobName.GENERATE_PREVIEW, JobName.GENERATE_THUMBHASH_THUMBNAIL],
},
{
item: { name: JobName.GENERATE_JPEG_THUMBNAIL, data: { id: 'asset-1', source: 'upload' } },
item: { name: JobName.GENERATE_THUMBNAIL, data: { id: 'asset-1', source: 'upload' } },
jobs: [
JobName.GENERATE_WEBP_THUMBNAIL,
JobName.GENERATE_PREVIEW,
JobName.GENERATE_THUMBHASH_THUMBNAIL,
JobName.SMART_SEARCH,
JobName.FACE_DETECTION,
@ -300,9 +300,9 @@ describe(JobService.name, () => {
],
},
{
item: { name: JobName.GENERATE_JPEG_THUMBNAIL, data: { id: 'asset-live-image', source: 'upload' } },
item: { name: JobName.GENERATE_THUMBNAIL, data: { id: 'asset-live-image', source: 'upload' } },
jobs: [
JobName.GENERATE_WEBP_THUMBNAIL,
JobName.GENERATE_PREVIEW,
JobName.GENERATE_THUMBHASH_THUMBNAIL,
JobName.SMART_SEARCH,
JobName.FACE_DETECTION,
@ -325,7 +325,7 @@ describe(JobService.name, () => {
for (const { item, jobs } of tests) {
it(`should queue ${jobs.length} jobs when a ${item.name} job finishes successfully`, async () => {
if (item.name === JobName.GENERATE_JPEG_THUMBNAIL && item.data.source === 'upload') {
if (item.name === JobName.GENERATE_THUMBNAIL && item.data.source === 'upload') {
if (item.data.id === 'asset-live-image') {
assetMock.getByIds.mockResolvedValue([assetStub.livePhotoStillAsset]);
} else {

View File

@ -233,7 +233,7 @@ export class JobService {
case JobName.STORAGE_TEMPLATE_MIGRATION_SINGLE: {
if (item.data.source === 'upload') {
await this.jobRepository.queue({ name: JobName.GENERATE_JPEG_THUMBNAIL, data: item.data });
await this.jobRepository.queue({ name: JobName.GENERATE_THUMBNAIL, data: item.data });
}
break;
}
@ -247,9 +247,9 @@ export class JobService {
break;
}
case JobName.GENERATE_JPEG_THUMBNAIL: {
case JobName.GENERATE_THUMBNAIL: {
const jobs: JobItem[] = [
{ name: JobName.GENERATE_WEBP_THUMBNAIL, data: item.data },
{ name: JobName.GENERATE_PREVIEW, data: item.data },
{ name: JobName.GENERATE_THUMBHASH_THUMBNAIL, data: item.data },
];
@ -270,7 +270,7 @@ export class JobService {
break;
}
case JobName.GENERATE_WEBP_THUMBNAIL: {
case JobName.GENERATE_PREVIEW: {
if (item.data.source !== 'upload') {
break;
}

View File

@ -4,6 +4,7 @@ import { ExifEntity } from 'src/entities/exif.entity';
import {
AudioCodec,
Colorspace,
ImageFormat,
SystemConfigKey,
ToneMapping,
TranscodeHWAccel,
@ -78,7 +79,7 @@ describe(MediaService.name, () => {
expect(assetMock.getWithout).not.toHaveBeenCalled();
expect(jobMock.queueAll).toHaveBeenCalledWith([
{
name: JobName.GENERATE_JPEG_THUMBNAIL,
name: JobName.GENERATE_THUMBNAIL,
data: { id: assetStub.image.id },
},
]);
@ -136,7 +137,7 @@ describe(MediaService.name, () => {
expect(assetMock.getWithout).toHaveBeenCalledWith({ skip: 0, take: 1000 }, WithoutProperty.THUMBNAIL);
expect(jobMock.queueAll).toHaveBeenCalledWith([
{
name: JobName.GENERATE_JPEG_THUMBNAIL,
name: JobName.GENERATE_THUMBNAIL,
data: { id: assetStub.image.id },
},
]);
@ -160,7 +161,7 @@ describe(MediaService.name, () => {
expect(assetMock.getWithout).toHaveBeenCalledWith({ skip: 0, take: 1000 }, WithoutProperty.THUMBNAIL);
expect(jobMock.queueAll).toHaveBeenCalledWith([
{
name: JobName.GENERATE_WEBP_THUMBNAIL,
name: JobName.GENERATE_PREVIEW,
data: { id: assetStub.image.id },
},
]);
@ -193,10 +194,10 @@ describe(MediaService.name, () => {
});
});
describe('handleGenerateJpegThumbnail', () => {
describe('handleGeneratePreview', () => {
it('should skip thumbnail generation if asset not found', async () => {
assetMock.getByIds.mockResolvedValue([]);
await sut.handleGenerateJpegThumbnail({ id: assetStub.image.id });
await sut.handleGeneratePreview({ id: assetStub.image.id });
expect(mediaMock.resize).not.toHaveBeenCalled();
expect(assetMock.update).not.toHaveBeenCalledWith();
});
@ -204,25 +205,29 @@ describe(MediaService.name, () => {
it('should skip video thumbnail generation if no video stream', async () => {
mediaMock.probe.mockResolvedValue(probeStub.noVideoStreams);
assetMock.getByIds.mockResolvedValue([assetStub.video]);
await sut.handleGenerateJpegThumbnail({ id: assetStub.image.id });
await sut.handleGeneratePreview({ id: assetStub.image.id });
expect(mediaMock.resize).not.toHaveBeenCalled();
expect(assetMock.update).not.toHaveBeenCalledWith();
});
it('should generate a thumbnail for an image', async () => {
assetMock.getByIds.mockResolvedValue([assetStub.image]);
await sut.handleGenerateJpegThumbnail({ id: assetStub.image.id });
await sut.handleGeneratePreview({ id: assetStub.image.id });
expect(storageMock.mkdirSync).toHaveBeenCalledWith('upload/thumbs/user-id/as/se');
expect(mediaMock.resize).toHaveBeenCalledWith('/original/path.jpg', 'upload/thumbs/user-id/as/se/asset-id.jpeg', {
expect(mediaMock.resize).toHaveBeenCalledWith(
'/original/path.jpg',
'upload/thumbs/user-id/as/se/asset-id-preview.jpeg',
{
size: 1440,
format: 'jpeg',
format: ImageFormat.JPEG,
quality: 80,
colorspace: Colorspace.SRGB,
});
},
);
expect(assetMock.update).toHaveBeenCalledWith({
id: 'asset-id',
resizePath: 'upload/thumbs/user-id/as/se/asset-id.jpeg',
previewPath: 'upload/thumbs/user-id/as/se/asset-id-preview.jpeg',
});
});
@ -230,30 +235,34 @@ describe(MediaService.name, () => {
assetMock.getByIds.mockResolvedValue([
{ ...assetStub.image, exifInfo: { profileDescription: 'Adobe RGB', bitsPerSample: 14 } as ExifEntity },
]);
await sut.handleGenerateJpegThumbnail({ id: assetStub.image.id });
await sut.handleGeneratePreview({ id: assetStub.image.id });
expect(storageMock.mkdirSync).toHaveBeenCalledWith('upload/thumbs/user-id/as/se');
expect(mediaMock.resize).toHaveBeenCalledWith('/original/path.jpg', 'upload/thumbs/user-id/as/se/asset-id.jpeg', {
expect(mediaMock.resize).toHaveBeenCalledWith(
'/original/path.jpg',
'upload/thumbs/user-id/as/se/asset-id-preview.jpeg',
{
size: 1440,
format: 'jpeg',
format: ImageFormat.JPEG,
quality: 80,
colorspace: Colorspace.P3,
});
},
);
expect(assetMock.update).toHaveBeenCalledWith({
id: 'asset-id',
resizePath: 'upload/thumbs/user-id/as/se/asset-id.jpeg',
previewPath: 'upload/thumbs/user-id/as/se/asset-id-preview.jpeg',
});
});
it('should generate a thumbnail for a video', async () => {
mediaMock.probe.mockResolvedValue(probeStub.videoStream2160p);
assetMock.getByIds.mockResolvedValue([assetStub.video]);
await sut.handleGenerateJpegThumbnail({ id: assetStub.video.id });
await sut.handleGeneratePreview({ id: assetStub.video.id });
expect(storageMock.mkdirSync).toHaveBeenCalledWith('upload/thumbs/user-id/as/se');
expect(mediaMock.transcode).toHaveBeenCalledWith(
'/original/path.ext',
'upload/thumbs/user-id/as/se/asset-id.jpeg',
'upload/thumbs/user-id/as/se/asset-id-preview.jpeg',
{
inputOptions: ['-ss 00:00:00', '-sws_flags accurate_rnd+bitexact+full_chroma_int'],
outputOptions: [
@ -266,19 +275,19 @@ describe(MediaService.name, () => {
);
expect(assetMock.update).toHaveBeenCalledWith({
id: 'asset-id',
resizePath: 'upload/thumbs/user-id/as/se/asset-id.jpeg',
previewPath: 'upload/thumbs/user-id/as/se/asset-id-preview.jpeg',
});
});
it('should tonemap thumbnail for hdr video', async () => {
mediaMock.probe.mockResolvedValue(probeStub.videoStreamHDR);
assetMock.getByIds.mockResolvedValue([assetStub.video]);
await sut.handleGenerateJpegThumbnail({ id: assetStub.video.id });
await sut.handleGeneratePreview({ id: assetStub.video.id });
expect(storageMock.mkdirSync).toHaveBeenCalledWith('upload/thumbs/user-id/as/se');
expect(mediaMock.transcode).toHaveBeenCalledWith(
'/original/path.ext',
'upload/thumbs/user-id/as/se/asset-id.jpeg',
'upload/thumbs/user-id/as/se/asset-id-preview.jpeg',
{
inputOptions: ['-ss 00:00:00', '-sws_flags accurate_rnd+bitexact+full_chroma_int'],
outputOptions: [
@ -291,7 +300,7 @@ describe(MediaService.name, () => {
);
expect(assetMock.update).toHaveBeenCalledWith({
id: 'asset-id',
resizePath: 'upload/thumbs/user-id/as/se/asset-id.jpeg',
previewPath: 'upload/thumbs/user-id/as/se/asset-id-preview.jpeg',
});
});
@ -302,11 +311,11 @@ describe(MediaService.name, () => {
{ key: SystemConfigKey.FFMPEG_MAX_BITRATE, value: '5000k' },
]);
assetMock.getByIds.mockResolvedValue([assetStub.video]);
await sut.handleGenerateJpegThumbnail({ id: assetStub.video.id });
await sut.handleGeneratePreview({ id: assetStub.video.id });
expect(mediaMock.transcode).toHaveBeenCalledWith(
'/original/path.ext',
'upload/thumbs/user-id/as/se/asset-id.jpeg',
'upload/thumbs/user-id/as/se/asset-id-preview.jpeg',
{
inputOptions: ['-ss 00:00:00', '-sws_flags accurate_rnd+bitexact+full_chroma_int'],
outputOptions: [
@ -321,31 +330,35 @@ describe(MediaService.name, () => {
it('should run successfully', async () => {
assetMock.getByIds.mockResolvedValue([assetStub.image]);
await sut.handleGenerateJpegThumbnail({ id: assetStub.image.id });
await sut.handleGeneratePreview({ id: assetStub.image.id });
});
});
describe('handleGenerateWebpThumbnail', () => {
describe('handleGenerateThumbnail', () => {
it('should skip thumbnail generation if asset not found', async () => {
assetMock.getByIds.mockResolvedValue([]);
await sut.handleGenerateWebpThumbnail({ id: assetStub.image.id });
await sut.handleGenerateThumbnail({ id: assetStub.image.id });
expect(mediaMock.resize).not.toHaveBeenCalled();
expect(assetMock.update).not.toHaveBeenCalledWith();
});
it('should generate a thumbnail', async () => {
assetMock.getByIds.mockResolvedValue([assetStub.image]);
await sut.handleGenerateWebpThumbnail({ id: assetStub.image.id });
await sut.handleGenerateThumbnail({ id: assetStub.image.id });
expect(mediaMock.resize).toHaveBeenCalledWith('/original/path.jpg', 'upload/thumbs/user-id/as/se/asset-id.webp', {
format: 'webp',
expect(mediaMock.resize).toHaveBeenCalledWith(
'/original/path.jpg',
'upload/thumbs/user-id/as/se/asset-id-thumbnail.webp',
{
format: ImageFormat.WEBP,
size: 250,
quality: 80,
colorspace: Colorspace.SRGB,
});
},
);
expect(assetMock.update).toHaveBeenCalledWith({
id: 'asset-id',
webpPath: 'upload/thumbs/user-id/as/se/asset-id.webp',
thumbnailPath: 'upload/thumbs/user-id/as/se/asset-id-thumbnail.webp',
});
});
});
@ -354,31 +367,35 @@ describe(MediaService.name, () => {
assetMock.getByIds.mockResolvedValue([
{ ...assetStub.image, exifInfo: { profileDescription: 'Adobe RGB', bitsPerSample: 14 } as ExifEntity },
]);
await sut.handleGenerateWebpThumbnail({ id: assetStub.image.id });
await sut.handleGenerateThumbnail({ id: assetStub.image.id });
expect(storageMock.mkdirSync).toHaveBeenCalledWith('upload/thumbs/user-id/as/se');
expect(mediaMock.resize).toHaveBeenCalledWith('/original/path.jpg', 'upload/thumbs/user-id/as/se/asset-id.webp', {
format: 'webp',
expect(mediaMock.resize).toHaveBeenCalledWith(
'/original/path.jpg',
'upload/thumbs/user-id/as/se/asset-id-thumbnail.webp',
{
format: ImageFormat.WEBP,
size: 250,
quality: 80,
colorspace: Colorspace.P3,
});
},
);
expect(assetMock.update).toHaveBeenCalledWith({
id: 'asset-id',
webpPath: 'upload/thumbs/user-id/as/se/asset-id.webp',
thumbnailPath: 'upload/thumbs/user-id/as/se/asset-id-thumbnail.webp',
});
});
describe('handleGenerateThumbhashThumbnail', () => {
it('should skip thumbhash generation if asset not found', async () => {
assetMock.getByIds.mockResolvedValue([]);
await sut.handleGenerateThumbhashThumbnail({ id: assetStub.image.id });
await sut.handleGenerateThumbhash({ id: assetStub.image.id });
expect(mediaMock.generateThumbhash).not.toHaveBeenCalled();
});
it('should skip thumbhash generation if resize path is missing', async () => {
assetMock.getByIds.mockResolvedValue([assetStub.noResizePath]);
await sut.handleGenerateThumbhashThumbnail({ id: assetStub.noResizePath.id });
await sut.handleGenerateThumbhash({ id: assetStub.noResizePath.id });
expect(mediaMock.generateThumbhash).not.toHaveBeenCalled();
});
@ -387,7 +404,7 @@ describe(MediaService.name, () => {
assetMock.getByIds.mockResolvedValue([assetStub.image]);
mediaMock.generateThumbhash.mockResolvedValue(thumbhashBuffer);
await sut.handleGenerateThumbhashThumbnail({ id: assetStub.image.id });
await sut.handleGenerateThumbhash({ id: assetStub.image.id });
expect(mediaMock.generateThumbhash).toHaveBeenCalledWith('/uploads/user-id/thumbs/path.jpg');
expect(assetMock.update).toHaveBeenCalledWith({ id: 'asset-id', thumbhash: thumbhashBuffer });

View File

@ -1,5 +1,5 @@
import { Inject, Injectable, UnsupportedMediaTypeException } from '@nestjs/common';
import { StorageCore, StorageFolder } from 'src/cores/storage.core';
import { GeneratedImageType, StorageCore, StorageFolder } from 'src/cores/storage.core';
import { SystemConfigCore } from 'src/cores/system-config.core';
import { SystemConfigFFmpegDto } from 'src/dtos/system-config.dto';
import { AssetEntity, AssetType } from 'src/entities/asset.entity';
@ -7,6 +7,7 @@ import { AssetPathType } from 'src/entities/move.entity';
import {
AudioCodec,
Colorspace,
ImageFormat,
TranscodeHWAccel,
TranscodePolicy,
TranscodeTarget,
@ -81,12 +82,12 @@ export class MediaService {
const jobs: JobItem[] = [];
for (const asset of assets) {
if (!asset.resizePath || force) {
jobs.push({ name: JobName.GENERATE_JPEG_THUMBNAIL, data: { id: asset.id } });
if (!asset.previewPath || force) {
jobs.push({ name: JobName.GENERATE_THUMBNAIL, data: { id: asset.id } });
continue;
}
if (!asset.webpPath) {
jobs.push({ name: JobName.GENERATE_WEBP_THUMBNAIL, data: { id: asset.id } });
if (!asset.thumbnailPath) {
jobs.push({ name: JobName.GENERATE_PREVIEW, data: { id: asset.id } });
}
if (!asset.thumbhash) {
jobs.push({ name: JobName.GENERATE_THUMBHASH_THUMBNAIL, data: { id: asset.id } });
@ -152,41 +153,41 @@ export class MediaService {
}
async handleAssetMigration({ id }: IEntityJob): Promise<JobStatus> {
const { image } = await this.configCore.getConfig();
const [asset] = await this.assetRepository.getByIds([id]);
if (!asset) {
return JobStatus.FAILED;
}
await this.storageCore.moveAssetFile(asset, AssetPathType.JPEG_THUMBNAIL);
await this.storageCore.moveAssetFile(asset, AssetPathType.WEBP_THUMBNAIL);
await this.storageCore.moveAssetFile(asset, AssetPathType.ENCODED_VIDEO);
await this.storageCore.moveAssetImage(asset, AssetPathType.PREVIEW, image.previewFormat);
await this.storageCore.moveAssetImage(asset, AssetPathType.THUMBNAIL, image.thumbnailFormat);
await this.storageCore.moveAssetVideo(asset);
return JobStatus.SUCCESS;
}
async handleGenerateJpegThumbnail({ id }: IEntityJob): Promise<JobStatus> {
async handleGeneratePreview({ id }: IEntityJob): Promise<JobStatus> {
const [asset] = await this.assetRepository.getByIds([id], { exifInfo: true });
if (!asset) {
return JobStatus.FAILED;
}
const resizePath = await this.generateThumbnail(asset, 'jpeg');
await this.assetRepository.update({ id: asset.id, resizePath });
const previewPath = await this.generateThumbnail(asset, AssetPathType.PREVIEW, ImageFormat.JPEG);
await this.assetRepository.update({ id: asset.id, previewPath });
return JobStatus.SUCCESS;
}
private async generateThumbnail(asset: AssetEntity, format: 'jpeg' | 'webp') {
const { thumbnail, ffmpeg } = await this.configCore.getConfig();
const size = format === 'jpeg' ? thumbnail.jpegSize : thumbnail.webpSize;
const path =
format === 'jpeg' ? StorageCore.getLargeThumbnailPath(asset) : StorageCore.getSmallThumbnailPath(asset);
private async generateThumbnail(asset: AssetEntity, type: GeneratedImageType, format: ImageFormat) {
const { image, ffmpeg } = await this.configCore.getConfig();
const size = type === AssetPathType.PREVIEW ? image.previewSize : image.thumbnailSize;
const path = StorageCore.getImagePath(asset, type, format);
this.storageCore.ensureFolders(path);
switch (asset.type) {
case AssetType.IMAGE: {
const colorspace = this.isSRGB(asset) ? Colorspace.SRGB : thumbnail.colorspace;
const thumbnailOptions = { format, size, colorspace, quality: thumbnail.quality };
await this.mediaRepository.resize(asset.originalPath, path, thumbnailOptions);
const colorspace = this.isSRGB(asset) ? Colorspace.SRGB : image.colorspace;
const imageOptions = { format, size, colorspace, quality: image.quality };
await this.mediaRepository.resize(asset.originalPath, path, imageOptions);
break;
}
@ -214,24 +215,24 @@ export class MediaService {
return path;
}
async handleGenerateWebpThumbnail({ id }: IEntityJob): Promise<JobStatus> {
async handleGenerateThumbnail({ id }: IEntityJob): Promise<JobStatus> {
const [asset] = await this.assetRepository.getByIds([id], { exifInfo: true });
if (!asset) {
return JobStatus.FAILED;
}
const webpPath = await this.generateThumbnail(asset, 'webp');
await this.assetRepository.update({ id: asset.id, webpPath });
const thumbnailPath = await this.generateThumbnail(asset, AssetPathType.THUMBNAIL, ImageFormat.WEBP);
await this.assetRepository.update({ id: asset.id, thumbnailPath });
return JobStatus.SUCCESS;
}
async handleGenerateThumbhashThumbnail({ id }: IEntityJob): Promise<JobStatus> {
async handleGenerateThumbhash({ id }: IEntityJob): Promise<JobStatus> {
const [asset] = await this.assetRepository.getByIds([id]);
if (!asset?.resizePath) {
if (!asset?.previewPath) {
return JobStatus.FAILED;
}
const thumbhash = await this.mediaRepository.generateThumbhash(asset.resizePath);
const thumbhash = await this.mediaRepository.generateThumbhash(asset.previewPath);
await this.assetRepository.update({ id: asset.id, thumbhash });
return JobStatus.SUCCESS;

View File

@ -53,9 +53,9 @@ export class MicroservicesService {
[JobName.MIGRATE_ASSET]: (data) => this.mediaService.handleAssetMigration(data),
[JobName.MIGRATE_PERSON]: (data) => this.personService.handlePersonMigration(data),
[JobName.QUEUE_GENERATE_THUMBNAILS]: (data) => this.mediaService.handleQueueGenerateThumbnails(data),
[JobName.GENERATE_JPEG_THUMBNAIL]: (data) => this.mediaService.handleGenerateJpegThumbnail(data),
[JobName.GENERATE_WEBP_THUMBNAIL]: (data) => this.mediaService.handleGenerateWebpThumbnail(data),
[JobName.GENERATE_THUMBHASH_THUMBNAIL]: (data) => this.mediaService.handleGenerateThumbhashThumbnail(data),
[JobName.GENERATE_THUMBNAIL]: (data) => this.mediaService.handleGeneratePreview(data),
[JobName.GENERATE_PREVIEW]: (data) => this.mediaService.handleGenerateThumbnail(data),
[JobName.GENERATE_THUMBHASH_THUMBNAIL]: (data) => this.mediaService.handleGenerateThumbhash(data),
[JobName.QUEUE_VIDEO_CONVERSION]: (data) => this.mediaService.handleQueueVideoConversion(data),
[JobName.VIDEO_CONVERSION]: (data) => this.mediaService.handleVideoConversion(data),
[JobName.QUEUE_METADATA_EXTRACTION]: (data) => this.metadataService.handleQueueMetadataExtraction(data),

View File

@ -645,7 +645,7 @@ describe(PersonService.name, () => {
expect(machineLearningMock.detectFaces).toHaveBeenCalledWith(
'http://immich-machine-learning:3003',
{
imagePath: assetStub.image.resizePath,
imagePath: assetStub.image.previewPath,
},
{
enabled: true,

View File

@ -23,6 +23,7 @@ import {
} from 'src/dtos/person.dto';
import { PersonPathType } from 'src/entities/move.entity';
import { PersonEntity } from 'src/entities/person.entity';
import { ImageFormat } from 'src/entities/system-config.entity';
import { IAccessRepository } from 'src/interfaces/access.interface';
import { IAssetRepository, WithoutProperty } from 'src/interfaces/asset.interface';
import { ICryptoRepository } from 'src/interfaces/crypto.interface';
@ -315,17 +316,17 @@ export class PersonService {
},
};
const [asset] = await this.assetRepository.getByIds([id], relations);
if (!asset || !asset.resizePath || asset.faces?.length > 0) {
if (!asset || !asset.previewPath || asset.faces?.length > 0) {
return JobStatus.FAILED;
}
const faces = await this.machineLearningRepository.detectFaces(
machineLearning.url,
{ imagePath: asset.resizePath },
{ imagePath: asset.previewPath },
machineLearning.facialRecognition,
);
this.logger.debug(`${faces.length} faces detected in ${asset.resizePath}`);
this.logger.debug(`${faces.length} faces detected in ${asset.previewPath}`);
this.logger.verbose(faces.map((face) => ({ ...face, embedding: `vector(${face.embedding.length})` })));
if (faces.length > 0) {
@ -470,7 +471,7 @@ export class PersonService {
}
async handleGeneratePersonThumbnail(data: IEntityJob): Promise<JobStatus> {
const { machineLearning, thumbnail } = await this.configCore.getConfig();
const { machineLearning, image } = await this.configCore.getConfig();
if (!machineLearning.enabled || !machineLearning.facialRecognition.enabled) {
return JobStatus.SKIPPED;
}
@ -496,7 +497,7 @@ export class PersonService {
} = face;
const [asset] = await this.assetRepository.getByIds([assetId]);
if (!asset?.resizePath) {
if (!asset?.previewPath) {
return JobStatus.FAILED;
}
this.logger.verbose(`Cropping face for person: ${person.id}`);
@ -527,12 +528,12 @@ export class PersonService {
height: newHalfSize * 2,
};
const croppedOutput = await this.mediaRepository.crop(asset.resizePath, cropOptions);
const croppedOutput = await this.mediaRepository.crop(asset.previewPath, cropOptions);
const thumbnailOptions = {
format: 'jpeg',
format: ImageFormat.JPEG,
size: FACE_THUMBNAIL_SIZE,
colorspace: thumbnail.colorspace,
quality: thumbnail.quality,
colorspace: image.colorspace,
quality: image.quality,
} as const;
await this.mediaRepository.resize(croppedOutput, thumbnailPath, thumbnailOptions);

View File

@ -18,7 +18,7 @@ import { newSystemConfigRepositoryMock } from 'test/repositories/system-config.r
const asset = {
id: 'asset-1',
resizePath: 'path/to/resize.ext',
previewPath: 'path/to/resize.ext',
} as AssetEntity;
describe(SmartInfoService.name, () => {
@ -94,7 +94,7 @@ describe(SmartInfoService.name, () => {
});
it('should skip assets without a resize path', async () => {
const asset = { resizePath: '' } as AssetEntity;
const asset = { previewPath: '' } as AssetEntity;
assetMock.getByIds.mockResolvedValue([asset]);
await sut.handleEncodeClip({ id: asset.id });

View File

@ -83,13 +83,13 @@ export class SmartInfoService {
return JobStatus.FAILED;
}
if (!asset.resizePath) {
if (!asset.previewPath) {
return JobStatus.FAILED;
}
const clipEmbedding = await this.machineLearning.encodeImage(
machineLearning.url,
{ imagePath: asset.resizePath },
{ imagePath: asset.previewPath },
machineLearning.clip,
);

View File

@ -4,6 +4,7 @@ import {
AudioCodec,
CQMode,
Colorspace,
ImageFormat,
LogLevel,
SystemConfig,
SystemConfigEntity,
@ -119,9 +120,11 @@ const updatedConfig = Object.freeze<SystemConfig>({
hashVerificationEnabled: true,
template: '{{y}}/{{y}}-{{MM}}-{{dd}}/{{filename}}',
},
thumbnail: {
webpSize: 250,
jpegSize: 1440,
image: {
thumbnailFormat: ImageFormat.WEBP,
thumbnailSize: 250,
previewFormat: ImageFormat.JPEG,
previewSize: 1440,
quality: 80,
colorspace: Colorspace.P3,
},

View File

@ -58,7 +58,7 @@ export function searchAssetBuilder(
builder.andWhere(`${builder.alias}.ownerId IN (:...userIds)`, { userIds: options.userIds });
}
const path = _.pick(options, ['encodedVideoPath', 'originalPath', 'resizePath', 'webpPath']);
const path = _.pick(options, ['encodedVideoPath', 'originalPath', 'previewPath', 'thumbnailPath']);
builder.andWhere(_.omitBy(path, _.isUndefined));
if (options.originalFileName) {

View File

@ -26,10 +26,10 @@ export const assetStub = {
ownerId: 'user-id',
deviceId: 'device-id',
originalPath: 'upload/library/IMG_123.jpg',
resizePath: null,
previewPath: null,
checksum: Buffer.from('file hash', 'utf8'),
type: AssetType.IMAGE,
webpPath: '/uploads/user-id/webp/path.ext',
thumbnailPath: '/uploads/user-id/webp/path.ext',
thumbhash: Buffer.from('blablabla', 'base64'),
encodedVideoPath: null,
createdAt: new Date('2023-02-23T05:06:29.716Z'),
@ -62,10 +62,10 @@ export const assetStub = {
ownerId: 'user-id',
deviceId: 'device-id',
originalPath: 'upload/library/IMG_456.jpg',
resizePath: '/uploads/user-id/thumbs/path.ext',
previewPath: '/uploads/user-id/thumbs/path.ext',
checksum: Buffer.from('file hash', 'utf8'),
type: AssetType.IMAGE,
webpPath: null,
thumbnailPath: null,
thumbhash: Buffer.from('blablabla', 'base64'),
encodedVideoPath: null,
createdAt: new Date('2023-02-23T05:06:29.716Z'),
@ -102,10 +102,10 @@ export const assetStub = {
ownerId: 'user-id',
deviceId: 'device-id',
originalPath: '/original/path.ext',
resizePath: '/uploads/user-id/thumbs/path.ext',
previewPath: '/uploads/user-id/thumbs/path.ext',
checksum: Buffer.from('file hash', 'utf8'),
type: AssetType.IMAGE,
webpPath: '/uploads/user-id/webp/path.ext',
thumbnailPath: '/uploads/user-id/webp/path.ext',
thumbhash: null,
encodedVideoPath: null,
createdAt: new Date('2023-02-23T05:06:29.716Z'),
@ -139,10 +139,10 @@ export const assetStub = {
ownerId: 'admin-id',
deviceId: 'device-id',
originalPath: '/original/path.jpg',
resizePath: '/uploads/admin-id/thumbs/path.jpg',
previewPath: '/uploads/admin-id/thumbs/path.jpg',
checksum: Buffer.from('file hash', 'utf8'),
type: AssetType.IMAGE,
webpPath: '/uploads/admin-id/webp/path.ext',
thumbnailPath: '/uploads/admin-id/webp/path.ext',
thumbhash: Buffer.from('blablabla', 'base64'),
encodedVideoPath: null,
createdAt: new Date('2023-02-23T05:06:29.716Z'),
@ -184,10 +184,10 @@ export const assetStub = {
ownerId: 'user-id',
deviceId: 'device-id',
originalPath: '/original/path.jpg',
resizePath: '/uploads/user-id/thumbs/path.jpg',
previewPath: '/uploads/user-id/thumbs/path.jpg',
checksum: Buffer.from('file hash', 'utf8'),
type: AssetType.IMAGE,
webpPath: '/uploads/user-id/webp/path.ext',
thumbnailPath: '/uploads/user-id/webp/path.ext',
thumbhash: Buffer.from('blablabla', 'base64'),
encodedVideoPath: null,
createdAt: new Date('2023-02-23T05:06:29.716Z'),
@ -224,10 +224,10 @@ export const assetStub = {
ownerId: 'user-id',
deviceId: 'device-id',
originalPath: '/data/user1/photo.jpg',
resizePath: '/uploads/user-id/thumbs/path.jpg',
previewPath: '/uploads/user-id/thumbs/path.jpg',
checksum: Buffer.from('file hash', 'utf8'),
type: AssetType.IMAGE,
webpPath: '/uploads/user-id/webp/path.ext',
thumbnailPath: '/uploads/user-id/webp/path.ext',
thumbhash: Buffer.from('blablabla', 'base64'),
encodedVideoPath: null,
createdAt: new Date('2023-02-23T05:06:29.716Z'),
@ -264,10 +264,10 @@ export const assetStub = {
ownerId: 'user-id',
deviceId: 'device-id',
originalPath: '/original/path.jpg',
resizePath: '/uploads/user-id/thumbs/path.jpg',
previewPath: '/uploads/user-id/thumbs/path.jpg',
checksum: Buffer.from('file hash', 'utf8'),
type: AssetType.IMAGE,
webpPath: '/uploads/user-id/webp/path.ext',
thumbnailPath: '/uploads/user-id/webp/path.ext',
thumbhash: Buffer.from('blablabla', 'base64'),
encodedVideoPath: null,
createdAt: new Date('2023-02-23T05:06:29.716Z'),
@ -304,10 +304,10 @@ export const assetStub = {
ownerId: 'user-id',
deviceId: 'device-id',
originalPath: '/original/path.ext',
resizePath: '/uploads/user-id/thumbs/path.ext',
previewPath: '/uploads/user-id/thumbs/path.ext',
checksum: Buffer.from('file hash', 'utf8'),
type: AssetType.IMAGE,
webpPath: '/uploads/user-id/webp/path.ext',
thumbnailPath: '/uploads/user-id/webp/path.ext',
thumbhash: Buffer.from('blablabla', 'base64'),
encodedVideoPath: null,
createdAt: new Date('2023-02-23T05:06:29.716Z'),
@ -344,10 +344,10 @@ export const assetStub = {
ownerId: 'user-id',
deviceId: 'device-id',
originalPath: '/original/path.ext',
resizePath: '/uploads/user-id/thumbs/path.ext',
previewPath: '/uploads/user-id/thumbs/path.ext',
checksum: Buffer.from('file hash', 'utf8'),
type: AssetType.IMAGE,
webpPath: '/uploads/user-id/webp/path.ext',
thumbnailPath: '/uploads/user-id/webp/path.ext',
thumbhash: Buffer.from('blablabla', 'base64'),
encodedVideoPath: null,
createdAt: new Date('2015-02-23T05:06:29.716Z'),
@ -385,10 +385,10 @@ export const assetStub = {
ownerId: 'user-id',
deviceId: 'device-id',
originalPath: '/original/path.ext',
resizePath: '/uploads/user-id/thumbs/path.ext',
previewPath: '/uploads/user-id/thumbs/path.ext',
checksum: Buffer.from('file hash', 'utf8'),
type: AssetType.VIDEO,
webpPath: null,
thumbnailPath: null,
thumbhash: null,
encodedVideoPath: null,
createdAt: new Date('2023-02-23T05:06:29.716Z'),
@ -456,10 +456,10 @@ export const assetStub = {
deviceId: 'device-id',
checksum: Buffer.from('file hash', 'utf8'),
originalPath: '/original/path.ext',
resizePath: '/uploads/user-id/thumbs/path.ext',
previewPath: '/uploads/user-id/thumbs/path.ext',
sidecarPath: null,
type: AssetType.IMAGE,
webpPath: null,
thumbnailPath: null,
thumbhash: null,
encodedVideoPath: null,
createdAt: new Date('2023-02-22T05:06:29.716Z'),
@ -499,11 +499,11 @@ export const assetStub = {
ownerId: 'user-id',
deviceId: 'device-id',
originalPath: '/original/path.ext',
resizePath: '/uploads/user-id/thumbs/path.ext',
previewPath: '/uploads/user-id/thumbs/path.ext',
thumbhash: null,
checksum: Buffer.from('file hash', 'utf8'),
type: AssetType.IMAGE,
webpPath: null,
thumbnailPath: null,
encodedVideoPath: null,
createdAt: new Date('2023-02-23T05:06:29.716Z'),
updatedAt: new Date('2023-02-23T05:06:29.716Z'),
@ -535,11 +535,11 @@ export const assetStub = {
ownerId: 'user-id',
deviceId: 'device-id',
originalPath: '/original/path.ext',
resizePath: '/uploads/user-id/thumbs/path.ext',
previewPath: '/uploads/user-id/thumbs/path.ext',
thumbhash: null,
checksum: Buffer.from('file hash', 'utf8'),
type: AssetType.IMAGE,
webpPath: null,
thumbnailPath: null,
encodedVideoPath: null,
createdAt: new Date('2023-02-23T05:06:29.716Z'),
updatedAt: new Date('2023-02-23T05:06:29.716Z'),
@ -572,11 +572,11 @@ export const assetStub = {
ownerId: 'user-id',
deviceId: 'device-id',
originalPath: '/original/path.ext',
resizePath: '/uploads/user-id/thumbs/path.ext',
previewPath: '/uploads/user-id/thumbs/path.ext',
thumbhash: null,
checksum: Buffer.from('file hash', 'utf8'),
type: AssetType.IMAGE,
webpPath: null,
thumbnailPath: null,
encodedVideoPath: null,
createdAt: new Date('2023-02-23T05:06:29.716Z'),
updatedAt: new Date('2023-02-23T05:06:29.716Z'),
@ -610,10 +610,10 @@ export const assetStub = {
ownerId: 'user-id',
deviceId: 'device-id',
originalPath: '/original/path.ext',
resizePath: '/uploads/user-id/thumbs/path.ext',
previewPath: '/uploads/user-id/thumbs/path.ext',
checksum: Buffer.from('file hash', 'utf8'),
type: AssetType.VIDEO,
webpPath: null,
thumbnailPath: null,
thumbhash: null,
encodedVideoPath: '/encoded/video/path.mp4',
createdAt: new Date('2023-02-23T05:06:29.716Z'),

View File

@ -199,7 +199,7 @@ export const sharedLinkStub = {
deviceId: 'device_id_1',
type: AssetType.VIDEO,
originalPath: 'fake_path/jpeg',
resizePath: '',
previewPath: '',
checksum: Buffer.from('file hash', 'utf8'),
fileModifiedAt: today,
fileCreatedAt: today,
@ -219,7 +219,7 @@ export const sharedLinkStub = {
objects: ['a', 'b', 'c'],
asset: null as any,
},
webpPath: '',
thumbnailPath: '',
thumbhash: null,
encodedVideoPath: '',
duration: null,

View File

@ -1,5 +1,5 @@
<script lang="ts">
import { Colorspace, type SystemConfigDto } from '@immich/sdk';
import { Colorspace, ImageFormat, type SystemConfigDto } from '@immich/sdk';
import { isEqual } from 'lodash-es';
import { createEventDispatcher } from 'svelte';
import { fade } from 'svelte/transition';
@ -25,10 +25,24 @@
<form autocomplete="off" on:submit|preventDefault>
<div class="ml-4 mt-4 flex flex-col gap-4">
<SettingSelect
label="SMALL THUMBNAIL RESOLUTION"
label="THUMBNAIL FORMAT"
desc="Format used for generated thumbnail images. WebP produces smaller files at the same quality."
number
bind:value={config.thumbnail.thumbnailFormat}
options={[
{ value: ImageFormat.Jpeg, text: 'JPEG' },
{ value: ImageFormat.Webp, text: 'WEBP' },
]}
name="resolution"
isEdited={config.thumbnail.thumbnailFormat !== savedConfig.thumbnail.thumbnailFormat}
{disabled}
/>
<SettingSelect
label="THUMBNAIL RESOLUTION"
desc="Used when viewing groups of photos (main timeline, album view, etc.). Higher resolutions can preserve more detail but take longer to encode, have larger file sizes, and can reduce app responsiveness."
number
bind:value={config.thumbnail.webpSize}
bind:value={config.thumbnail.thumbnailSize}
options={[
{ value: 1080, text: '1080p' },
{ value: 720, text: '720p' },
@ -37,15 +51,29 @@
{ value: 200, text: '200p' },
]}
name="resolution"
isEdited={config.thumbnail.webpSize !== savedConfig.thumbnail.webpSize}
isEdited={config.thumbnail.thumbnailSize !== savedConfig.thumbnail.thumbnailSize}
{disabled}
/>
<SettingSelect
label="LARGE THUMBNAIL RESOLUTION"
label="PREVIEW FORMAT"
desc="Format used for generated preview images. WebP produces smaller files at the same quality."
number
bind:value={config.thumbnail.thumbnailFormat}
options={[
{ value: ImageFormat.Jpeg, text: 'JPEG' },
{ value: ImageFormat.Webp, text: 'WEBP' },
]}
name="resolution"
isEdited={config.thumbnail.thumbnailFormat !== savedConfig.thumbnail.thumbnailFormat}
{disabled}
/>
<SettingSelect
label="PREVIEW RESOLUTION"
desc="Used when viewing a single photo and for machine learning. Higher resolutions can preserve more detail but take longer to encode, have larger file sizes, and can reduce app responsiveness."
number
bind:value={config.thumbnail.jpegSize}
bind:value={config.thumbnail.previewSize}
options={[
{ value: 2160, text: '4K' },
{ value: 1440, text: '1440p' },
@ -53,14 +81,14 @@
{ value: 720, text: '720p' },
]}
name="resolution"
isEdited={config.thumbnail.jpegSize !== savedConfig.thumbnail.jpegSize}
isEdited={config.thumbnail.previewSize !== savedConfig.thumbnail.previewSize}
{disabled}
/>
<SettingInputField
inputType={SettingInputFieldType.NUMBER}
label="QUALITY"
desc="Thumbnail quality from 1-100. Higher is better for quality but produces larger files."
desc="Image quality from 1-100. Higher is better for quality but produces larger files."
bind:value={config.thumbnail.quality}
isEdited={config.thumbnail.quality !== savedConfig.thumbnail.quality}
/>

View File

@ -13,7 +13,7 @@
import SettingAccordion from '$lib/components/shared-components/settings/setting-accordion.svelte';
import StorageTemplateSettings from '$lib/components/admin-page/settings/storage-template/storage-template-settings.svelte';
import ThemeSettings from '$lib/components/admin-page/settings/theme/theme-settings.svelte';
import ThumbnailSettings from '$lib/components/admin-page/settings/thumbnail/thumbnail-settings.svelte';
import ImageSettings from '$lib/components/admin-page/settings/image/image-settings.svelte';
import TrashSettings from '$lib/components/admin-page/settings/trash-settings/trash-settings.svelte';
import UserSettings from '$lib/components/admin-page/settings/user-settings/user-settings.svelte';
import LinkButton from '$lib/components/elements/buttons/link-button.svelte';
@ -43,7 +43,7 @@
| typeof ServerSettings
| typeof StorageTemplateSettings
| typeof ThemeSettings
| typeof ThumbnailSettings
| typeof ImageSettings
| typeof TrashSettings
| typeof NewVersionCheckSettings
| typeof FFmpegSettings
@ -64,6 +64,12 @@
subtitle: string;
key: string;
}> = [
{
item: ImageSettings,
title: 'Image Settings',
subtitle: 'Manage the format, quality and resolution of generated images',
key: 'image',
},
{
item: JobSettings,
title: 'Job Settings',
@ -124,12 +130,6 @@
subtitle: 'Manage customization of the Immich web interface',
key: 'theme',
},
{
item: ThumbnailSettings,
title: 'Thumbnail Settings',
subtitle: 'Manage the resolution of thumbnail sizes',
key: 'thumbnail',
},
{
item: TrashSettings,
title: 'Trash Settings',