From dd7c06f937e8adcf30dc322cd455cb334931d9c1 Mon Sep 17 00:00:00 2001 From: mertalev <101130780+mertalev@users.noreply.github.com> Date: Mon, 16 Jun 2025 13:23:00 -0400 Subject: [PATCH] update api --- mobile/openapi/README.md | 2 - mobile/openapi/lib/api.dart | 1 - mobile/openapi/lib/api/search_api.dart | 60 +- mobile/openapi/lib/api_client.dart | 2 - .../lib/model/metadata_search_dto.dart | 19 +- mobile/openapi/lib/model/ocr_config.dart | 35 +- mobile/openapi/lib/model/ocr_search_dto.dart | 522 ------------------ .../openapi/lib/model/random_search_dto.dart | 19 +- .../openapi/lib/model/smart_search_dto.dart | 19 +- .../lib/model/statistics_search_dto.dart | 19 +- open-api/immich-openapi-specs.json | 220 +------- open-api/typescript-sdk/src/fetch-client.ts | 55 +- server/src/config.ts | 4 +- server/src/dtos/search.dto.ts | 17 +- .../src/repositories/asset-job.repository.ts | 16 +- server/src/repositories/ocr.repository.ts | 5 +- server/src/schema/index.ts | 2 + server/src/schema/tables/asset-ocr.table.ts | 4 +- server/src/services/job.service.ts | 1 - server/src/services/ocr.service.ts | 14 +- server/src/utils/database.ts | 2 +- .../search-bar/search-bar.svelte | 2 +- 22 files changed, 177 insertions(+), 863 deletions(-) delete mode 100644 mobile/openapi/lib/model/ocr_search_dto.dart diff --git a/mobile/openapi/README.md b/mobile/openapi/README.md index e9f959d022..bca1b5219a 100644 --- a/mobile/openapi/README.md +++ b/mobile/openapi/README.md @@ -194,7 +194,6 @@ Class | Method | HTTP request | Description *SearchApi* | [**searchAssetStatistics**](doc//SearchApi.md#searchassetstatistics) | **POST** /search/statistics | *SearchApi* | [**searchAssets**](doc//SearchApi.md#searchassets) | **POST** /search/metadata | *SearchApi* | [**searchLargeAssets**](doc//SearchApi.md#searchlargeassets) | **POST** /search/large-assets | -*SearchApi* | [**searchOcr**](doc//SearchApi.md#searchocr) | **POST** /search/ocr | *SearchApi* | [**searchPerson**](doc//SearchApi.md#searchperson) | **GET** /search/person | *SearchApi* | [**searchPlaces**](doc//SearchApi.md#searchplaces) | **GET** /search/places | *SearchApi* | [**searchRandom**](doc//SearchApi.md#searchrandom) | **POST** /search/random | @@ -417,7 +416,6 @@ Class | Method | HTTP request | Description - [OAuthConfigDto](doc//OAuthConfigDto.md) - [OAuthTokenEndpointAuthMethod](doc//OAuthTokenEndpointAuthMethod.md) - [OcrConfig](doc//OcrConfig.md) - - [OcrSearchDto](doc//OcrSearchDto.md) - [OnThisDayDto](doc//OnThisDayDto.md) - [OnboardingDto](doc//OnboardingDto.md) - [OnboardingResponseDto](doc//OnboardingResponseDto.md) diff --git a/mobile/openapi/lib/api.dart b/mobile/openapi/lib/api.dart index 5af0fda839..b09e53f402 100644 --- a/mobile/openapi/lib/api.dart +++ b/mobile/openapi/lib/api.dart @@ -188,7 +188,6 @@ part 'model/o_auth_callback_dto.dart'; part 'model/o_auth_config_dto.dart'; part 'model/o_auth_token_endpoint_auth_method.dart'; part 'model/ocr_config.dart'; -part 'model/ocr_search_dto.dart'; part 'model/on_this_day_dto.dart'; part 'model/onboarding_dto.dart'; part 'model/onboarding_response_dto.dart'; diff --git a/mobile/openapi/lib/api/search_api.dart b/mobile/openapi/lib/api/search_api.dart index df0ad8d4c0..6a6c28a7e1 100644 --- a/mobile/openapi/lib/api/search_api.dart +++ b/mobile/openapi/lib/api/search_api.dart @@ -346,6 +346,8 @@ class SearchApi { /// /// * [String] model: /// + /// * [String] ocr: + /// /// * [List] personIds: /// /// * [num] rating: @@ -375,7 +377,7 @@ class SearchApi { /// * [bool] withDeleted: /// /// * [bool] withExif: - Future searchLargeAssetsWithHttpInfo({ List? albumIds, String? city, String? country, DateTime? createdAfter, DateTime? createdBefore, String? deviceId, bool? isEncoded, bool? isFavorite, bool? isMotion, bool? isNotInAlbum, bool? isOffline, String? lensModel, String? libraryId, String? make, int? minFileSize, String? model, List? personIds, num? rating, num? size, String? state, List? tagIds, DateTime? takenAfter, DateTime? takenBefore, DateTime? trashedAfter, DateTime? trashedBefore, AssetTypeEnum? type, DateTime? updatedAfter, DateTime? updatedBefore, AssetVisibility? visibility, bool? withDeleted, bool? withExif, }) async { + Future searchLargeAssetsWithHttpInfo({ List? albumIds, String? city, String? country, DateTime? createdAfter, DateTime? createdBefore, String? deviceId, bool? isEncoded, bool? isFavorite, bool? isMotion, bool? isNotInAlbum, bool? isOffline, String? lensModel, String? libraryId, String? make, int? minFileSize, String? model, String? ocr, List? personIds, num? rating, num? size, String? state, List? tagIds, DateTime? takenAfter, DateTime? takenBefore, DateTime? trashedAfter, DateTime? trashedBefore, AssetTypeEnum? type, DateTime? updatedAfter, DateTime? updatedBefore, AssetVisibility? visibility, bool? withDeleted, bool? withExif, }) async { // ignore: prefer_const_declarations final apiPath = r'/search/large-assets'; @@ -434,6 +436,9 @@ class SearchApi { if (model != null) { queryParams.addAll(_queryParams('', 'model', model)); } + if (ocr != null) { + queryParams.addAll(_queryParams('', 'ocr', ocr)); + } if (personIds != null) { queryParams.addAll(_queryParams('multi', 'personIds', personIds)); } @@ -530,6 +535,8 @@ class SearchApi { /// /// * [String] model: /// + /// * [String] ocr: + /// /// * [List] personIds: /// /// * [num] rating: @@ -559,8 +566,8 @@ class SearchApi { /// * [bool] withDeleted: /// /// * [bool] withExif: - Future?> searchLargeAssets({ List? albumIds, String? city, String? country, DateTime? createdAfter, DateTime? createdBefore, String? deviceId, bool? isEncoded, bool? isFavorite, bool? isMotion, bool? isNotInAlbum, bool? isOffline, String? lensModel, String? libraryId, String? make, int? minFileSize, String? model, List? personIds, num? rating, num? size, String? state, List? tagIds, DateTime? takenAfter, DateTime? takenBefore, DateTime? trashedAfter, DateTime? trashedBefore, AssetTypeEnum? type, DateTime? updatedAfter, DateTime? updatedBefore, AssetVisibility? visibility, bool? withDeleted, bool? withExif, }) async { - final response = await searchLargeAssetsWithHttpInfo( albumIds: albumIds, city: city, country: country, createdAfter: createdAfter, createdBefore: createdBefore, deviceId: deviceId, isEncoded: isEncoded, isFavorite: isFavorite, isMotion: isMotion, isNotInAlbum: isNotInAlbum, isOffline: isOffline, lensModel: lensModel, libraryId: libraryId, make: make, minFileSize: minFileSize, model: model, personIds: personIds, rating: rating, size: size, state: state, tagIds: tagIds, takenAfter: takenAfter, takenBefore: takenBefore, trashedAfter: trashedAfter, trashedBefore: trashedBefore, type: type, updatedAfter: updatedAfter, updatedBefore: updatedBefore, visibility: visibility, withDeleted: withDeleted, withExif: withExif, ); + Future?> searchLargeAssets({ List? albumIds, String? city, String? country, DateTime? createdAfter, DateTime? createdBefore, String? deviceId, bool? isEncoded, bool? isFavorite, bool? isMotion, bool? isNotInAlbum, bool? isOffline, String? lensModel, String? libraryId, String? make, int? minFileSize, String? model, String? ocr, List? personIds, num? rating, num? size, String? state, List? tagIds, DateTime? takenAfter, DateTime? takenBefore, DateTime? trashedAfter, DateTime? trashedBefore, AssetTypeEnum? type, DateTime? updatedAfter, DateTime? updatedBefore, AssetVisibility? visibility, bool? withDeleted, bool? withExif, }) async { + final response = await searchLargeAssetsWithHttpInfo( albumIds: albumIds, city: city, country: country, createdAfter: createdAfter, createdBefore: createdBefore, deviceId: deviceId, isEncoded: isEncoded, isFavorite: isFavorite, isMotion: isMotion, isNotInAlbum: isNotInAlbum, isOffline: isOffline, lensModel: lensModel, libraryId: libraryId, make: make, minFileSize: minFileSize, model: model, ocr: ocr, personIds: personIds, rating: rating, size: size, state: state, tagIds: tagIds, takenAfter: takenAfter, takenBefore: takenBefore, trashedAfter: trashedAfter, trashedBefore: trashedBefore, type: type, updatedAfter: updatedAfter, updatedBefore: updatedBefore, visibility: visibility, withDeleted: withDeleted, withExif: withExif, ); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -577,53 +584,6 @@ class SearchApi { return null; } - /// Performs an HTTP 'POST /search/ocr' operation and returns the [Response]. - /// Parameters: - /// - /// * [OcrSearchDto] ocrSearchDto (required): - Future searchOcrWithHttpInfo(OcrSearchDto ocrSearchDto,) async { - // ignore: prefer_const_declarations - final apiPath = r'/search/ocr'; - - // ignore: prefer_final_locals - Object? postBody = ocrSearchDto; - - final queryParams = []; - final headerParams = {}; - final formParams = {}; - - const contentTypes = ['application/json']; - - - return apiClient.invokeAPI( - apiPath, - 'POST', - queryParams, - postBody, - headerParams, - formParams, - contentTypes.isEmpty ? null : contentTypes.first, - ); - } - - /// Parameters: - /// - /// * [OcrSearchDto] ocrSearchDto (required): - Future searchOcr(OcrSearchDto ocrSearchDto,) async { - final response = await searchOcrWithHttpInfo(ocrSearchDto,); - if (response.statusCode >= HttpStatus.badRequest) { - throw ApiException(response.statusCode, await _decodeBodyBytes(response)); - } - // When a remote server returns no body with a status of 204, we shall not decode it. - // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" - // FormatException when trying to decode an empty string. - if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { - return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'SearchResponseDto',) as SearchResponseDto; - - } - return null; - } - /// This endpoint requires the `person.read` permission. /// /// Note: This method returns the HTTP [Response]. diff --git a/mobile/openapi/lib/api_client.dart b/mobile/openapi/lib/api_client.dart index ce56cfd4b4..5061f9a64e 100644 --- a/mobile/openapi/lib/api_client.dart +++ b/mobile/openapi/lib/api_client.dart @@ -430,8 +430,6 @@ class ApiClient { return OAuthTokenEndpointAuthMethodTypeTransformer().decode(value); case 'OcrConfig': return OcrConfig.fromJson(value); - case 'OcrSearchDto': - return OcrSearchDto.fromJson(value); case 'OnThisDayDto': return OnThisDayDto.fromJson(value); case 'OnboardingDto': diff --git a/mobile/openapi/lib/model/metadata_search_dto.dart b/mobile/openapi/lib/model/metadata_search_dto.dart index b7e637d4b4..7d8d2b1314 100644 --- a/mobile/openapi/lib/model/metadata_search_dto.dart +++ b/mobile/openapi/lib/model/metadata_search_dto.dart @@ -33,6 +33,7 @@ class MetadataSearchDto { this.libraryId, this.make, this.model, + this.ocr, this.order = AssetOrder.desc, this.originalFileName, this.originalPath, @@ -182,6 +183,14 @@ class MetadataSearchDto { String? model; + /// + /// 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? ocr; + AssetOrder order; /// @@ -369,6 +378,7 @@ class MetadataSearchDto { other.libraryId == libraryId && other.make == make && other.model == model && + other.ocr == ocr && other.order == order && other.originalFileName == originalFileName && other.originalPath == originalPath && @@ -416,6 +426,7 @@ class MetadataSearchDto { (libraryId == null ? 0 : libraryId!.hashCode) + (make == null ? 0 : make!.hashCode) + (model == null ? 0 : model!.hashCode) + + (ocr == null ? 0 : ocr!.hashCode) + (order.hashCode) + (originalFileName == null ? 0 : originalFileName!.hashCode) + (originalPath == null ? 0 : originalPath!.hashCode) + @@ -441,7 +452,7 @@ class MetadataSearchDto { (withStacked == null ? 0 : withStacked!.hashCode); @override - String toString() => 'MetadataSearchDto[albumIds=$albumIds, checksum=$checksum, city=$city, country=$country, createdAfter=$createdAfter, createdBefore=$createdBefore, description=$description, deviceAssetId=$deviceAssetId, deviceId=$deviceId, encodedVideoPath=$encodedVideoPath, id=$id, isEncoded=$isEncoded, isFavorite=$isFavorite, isMotion=$isMotion, isNotInAlbum=$isNotInAlbum, isOffline=$isOffline, lensModel=$lensModel, libraryId=$libraryId, make=$make, model=$model, order=$order, originalFileName=$originalFileName, originalPath=$originalPath, page=$page, personIds=$personIds, previewPath=$previewPath, rating=$rating, size=$size, state=$state, tagIds=$tagIds, takenAfter=$takenAfter, takenBefore=$takenBefore, thumbnailPath=$thumbnailPath, trashedAfter=$trashedAfter, trashedBefore=$trashedBefore, type=$type, updatedAfter=$updatedAfter, updatedBefore=$updatedBefore, visibility=$visibility, withDeleted=$withDeleted, withExif=$withExif, withPeople=$withPeople, withStacked=$withStacked]'; + String toString() => 'MetadataSearchDto[albumIds=$albumIds, checksum=$checksum, city=$city, country=$country, createdAfter=$createdAfter, createdBefore=$createdBefore, description=$description, deviceAssetId=$deviceAssetId, deviceId=$deviceId, encodedVideoPath=$encodedVideoPath, id=$id, isEncoded=$isEncoded, isFavorite=$isFavorite, isMotion=$isMotion, isNotInAlbum=$isNotInAlbum, isOffline=$isOffline, lensModel=$lensModel, libraryId=$libraryId, make=$make, model=$model, ocr=$ocr, order=$order, originalFileName=$originalFileName, originalPath=$originalPath, page=$page, personIds=$personIds, previewPath=$previewPath, rating=$rating, size=$size, state=$state, tagIds=$tagIds, takenAfter=$takenAfter, takenBefore=$takenBefore, thumbnailPath=$thumbnailPath, trashedAfter=$trashedAfter, trashedBefore=$trashedBefore, type=$type, updatedAfter=$updatedAfter, updatedBefore=$updatedBefore, visibility=$visibility, withDeleted=$withDeleted, withExif=$withExif, withPeople=$withPeople, withStacked=$withStacked]'; Map toJson() { final json = {}; @@ -540,6 +551,11 @@ class MetadataSearchDto { json[r'model'] = this.model; } else { // json[r'model'] = null; + } + if (this.ocr != null) { + json[r'ocr'] = this.ocr; + } else { + // json[r'ocr'] = null; } json[r'order'] = this.order; if (this.originalFileName != null) { @@ -682,6 +698,7 @@ class MetadataSearchDto { libraryId: mapValueOfType(json, r'libraryId'), make: mapValueOfType(json, r'make'), model: mapValueOfType(json, r'model'), + ocr: mapValueOfType(json, r'ocr'), order: AssetOrder.fromJson(json[r'order']) ?? AssetOrder.desc, originalFileName: mapValueOfType(json, r'originalFileName'), originalPath: mapValueOfType(json, r'originalPath'), diff --git a/mobile/openapi/lib/model/ocr_config.dart b/mobile/openapi/lib/model/ocr_config.dart index 0ea4d7697e..51746c4924 100644 --- a/mobile/openapi/lib/model/ocr_config.dart +++ b/mobile/openapi/lib/model/ocr_config.dart @@ -14,38 +14,53 @@ class OcrConfig { /// Returns a new [OcrConfig] instance. OcrConfig({ required this.enabled, - required this.minScore, + required this.maxResolution, + required this.minDetectionScore, + required this.minRecognitionScore, required this.modelName, }); bool enabled; + /// Minimum value: 1 + int maxResolution; + /// Minimum value: 0.1 /// Maximum value: 1 - double minScore; + double minDetectionScore; + + /// Minimum value: 0.1 + /// Maximum value: 1 + double minRecognitionScore; String modelName; @override bool operator ==(Object other) => identical(this, other) || other is OcrConfig && other.enabled == enabled && - other.minScore == minScore && + other.maxResolution == maxResolution && + other.minDetectionScore == minDetectionScore && + other.minRecognitionScore == minRecognitionScore && other.modelName == modelName; @override int get hashCode => // ignore: unnecessary_parenthesis (enabled.hashCode) + - (minScore.hashCode) + + (maxResolution.hashCode) + + (minDetectionScore.hashCode) + + (minRecognitionScore.hashCode) + (modelName.hashCode); @override - String toString() => 'OcrConfig[enabled=$enabled, minScore=$minScore, modelName=$modelName]'; + String toString() => 'OcrConfig[enabled=$enabled, maxResolution=$maxResolution, minDetectionScore=$minDetectionScore, minRecognitionScore=$minRecognitionScore, modelName=$modelName]'; Map toJson() { final json = {}; json[r'enabled'] = this.enabled; - json[r'minScore'] = this.minScore; + json[r'maxResolution'] = this.maxResolution; + json[r'minDetectionScore'] = this.minDetectionScore; + json[r'minRecognitionScore'] = this.minRecognitionScore; json[r'modelName'] = this.modelName; return json; } @@ -60,7 +75,9 @@ class OcrConfig { return OcrConfig( enabled: mapValueOfType(json, r'enabled')!, - minScore: (mapValueOfType(json, r'minScore')!).toDouble(), + maxResolution: mapValueOfType(json, r'maxResolution')!, + minDetectionScore: (mapValueOfType(json, r'minDetectionScore')!).toDouble(), + minRecognitionScore: (mapValueOfType(json, r'minRecognitionScore')!).toDouble(), modelName: mapValueOfType(json, r'modelName')!, ); } @@ -110,7 +127,9 @@ class OcrConfig { /// The list of required keys that must be present in a JSON. static const requiredKeys = { 'enabled', - 'minScore', + 'maxResolution', + 'minDetectionScore', + 'minRecognitionScore', 'modelName', }; } diff --git a/mobile/openapi/lib/model/ocr_search_dto.dart b/mobile/openapi/lib/model/ocr_search_dto.dart deleted file mode 100644 index a9a6983c35..0000000000 --- a/mobile/openapi/lib/model/ocr_search_dto.dart +++ /dev/null @@ -1,522 +0,0 @@ -// -// AUTO-GENERATED FILE, DO NOT MODIFY! -// -// @dart=2.18 - -// 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 OcrSearchDto { - /// Returns a new [OcrSearchDto] instance. - OcrSearchDto({ - this.albumIds = const [], - this.city, - this.country, - this.createdAfter, - this.createdBefore, - this.deviceId, - this.isEncoded, - this.isFavorite, - this.isMotion, - this.isNotInAlbum, - this.isOffline, - this.lensModel, - this.libraryId, - this.make, - this.model, - required this.ocr, - this.page, - this.personIds = const [], - this.rating, - this.state, - this.tagIds = const [], - this.takenAfter, - this.takenBefore, - this.trashedAfter, - this.trashedBefore, - this.type, - this.updatedAfter, - this.updatedBefore, - this.visibility, - }); - - List albumIds; - - String? city; - - String? country; - - /// - /// 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. - /// - DateTime? createdAfter; - - /// - /// 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. - /// - DateTime? createdBefore; - - /// - /// 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? deviceId; - - /// - /// 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. - /// - bool? isEncoded; - - /// - /// 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. - /// - bool? isFavorite; - - /// - /// 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. - /// - bool? isMotion; - - /// - /// 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. - /// - bool? isNotInAlbum; - - /// - /// 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. - /// - bool? isOffline; - - String? lensModel; - - String? libraryId; - - /// - /// 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? make; - - String? model; - - String ocr; - - /// Minimum value: 1 - /// - /// 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. - /// - num? page; - - List personIds; - - /// Minimum value: -1 - /// Maximum value: 5 - /// - /// 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. - /// - num? rating; - - String? state; - - List? tagIds; - - /// - /// 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. - /// - DateTime? takenAfter; - - /// - /// 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. - /// - 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. - /// - DateTime? trashedAfter; - - /// - /// 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. - /// - DateTime? trashedBefore; - - /// - /// 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. - /// - AssetTypeEnum? type; - - /// - /// 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. - /// - DateTime? updatedAfter; - - /// - /// 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. - /// - 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. - /// - AssetVisibility? visibility; - - @override - bool operator ==(Object other) => identical(this, other) || other is OcrSearchDto && - _deepEquality.equals(other.albumIds, albumIds) && - other.city == city && - other.country == country && - other.createdAfter == createdAfter && - other.createdBefore == createdBefore && - other.deviceId == deviceId && - other.isEncoded == isEncoded && - other.isFavorite == isFavorite && - other.isMotion == isMotion && - other.isNotInAlbum == isNotInAlbum && - other.isOffline == isOffline && - other.lensModel == lensModel && - other.libraryId == libraryId && - other.make == make && - other.model == model && - other.ocr == ocr && - other.page == page && - _deepEquality.equals(other.personIds, personIds) && - other.rating == rating && - other.state == state && - _deepEquality.equals(other.tagIds, tagIds) && - other.takenAfter == takenAfter && - other.takenBefore == takenBefore && - other.trashedAfter == trashedAfter && - other.trashedBefore == trashedBefore && - other.type == type && - other.updatedAfter == updatedAfter && - other.updatedBefore == updatedBefore && - other.visibility == visibility; - - @override - int get hashCode => - // ignore: unnecessary_parenthesis - (albumIds.hashCode) + - (city == null ? 0 : city!.hashCode) + - (country == null ? 0 : country!.hashCode) + - (createdAfter == null ? 0 : createdAfter!.hashCode) + - (createdBefore == null ? 0 : createdBefore!.hashCode) + - (deviceId == null ? 0 : deviceId!.hashCode) + - (isEncoded == null ? 0 : isEncoded!.hashCode) + - (isFavorite == null ? 0 : isFavorite!.hashCode) + - (isMotion == null ? 0 : isMotion!.hashCode) + - (isNotInAlbum == null ? 0 : isNotInAlbum!.hashCode) + - (isOffline == null ? 0 : isOffline!.hashCode) + - (lensModel == null ? 0 : lensModel!.hashCode) + - (libraryId == null ? 0 : libraryId!.hashCode) + - (make == null ? 0 : make!.hashCode) + - (model == null ? 0 : model!.hashCode) + - (ocr.hashCode) + - (page == null ? 0 : page!.hashCode) + - (personIds.hashCode) + - (rating == null ? 0 : rating!.hashCode) + - (state == null ? 0 : state!.hashCode) + - (tagIds == null ? 0 : tagIds!.hashCode) + - (takenAfter == null ? 0 : takenAfter!.hashCode) + - (takenBefore == null ? 0 : takenBefore!.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) + - (visibility == null ? 0 : visibility!.hashCode); - - @override - String toString() => 'OcrSearchDto[albumIds=$albumIds, city=$city, country=$country, createdAfter=$createdAfter, createdBefore=$createdBefore, deviceId=$deviceId, isEncoded=$isEncoded, isFavorite=$isFavorite, isMotion=$isMotion, isNotInAlbum=$isNotInAlbum, isOffline=$isOffline, lensModel=$lensModel, libraryId=$libraryId, make=$make, model=$model, ocr=$ocr, page=$page, personIds=$personIds, rating=$rating, state=$state, tagIds=$tagIds, takenAfter=$takenAfter, takenBefore=$takenBefore, trashedAfter=$trashedAfter, trashedBefore=$trashedBefore, type=$type, updatedAfter=$updatedAfter, updatedBefore=$updatedBefore, visibility=$visibility]'; - - Map toJson() { - final json = {}; - json[r'albumIds'] = this.albumIds; - if (this.city != null) { - json[r'city'] = this.city; - } else { - // json[r'city'] = null; - } - if (this.country != null) { - json[r'country'] = this.country; - } else { - // json[r'country'] = null; - } - if (this.createdAfter != null) { - json[r'createdAfter'] = this.createdAfter!.toUtc().toIso8601String(); - } else { - // json[r'createdAfter'] = null; - } - if (this.createdBefore != null) { - json[r'createdBefore'] = this.createdBefore!.toUtc().toIso8601String(); - } else { - // json[r'createdBefore'] = null; - } - if (this.deviceId != null) { - json[r'deviceId'] = this.deviceId; - } else { - // json[r'deviceId'] = null; - } - if (this.isEncoded != null) { - json[r'isEncoded'] = this.isEncoded; - } else { - // json[r'isEncoded'] = null; - } - if (this.isFavorite != null) { - json[r'isFavorite'] = this.isFavorite; - } else { - // json[r'isFavorite'] = null; - } - if (this.isMotion != null) { - json[r'isMotion'] = this.isMotion; - } else { - // json[r'isMotion'] = null; - } - if (this.isNotInAlbum != null) { - json[r'isNotInAlbum'] = this.isNotInAlbum; - } else { - // json[r'isNotInAlbum'] = null; - } - if (this.isOffline != null) { - json[r'isOffline'] = this.isOffline; - } else { - // json[r'isOffline'] = null; - } - if (this.lensModel != null) { - json[r'lensModel'] = this.lensModel; - } else { - // json[r'lensModel'] = null; - } - if (this.libraryId != null) { - json[r'libraryId'] = this.libraryId; - } else { - // json[r'libraryId'] = null; - } - if (this.make != null) { - json[r'make'] = this.make; - } else { - // json[r'make'] = null; - } - if (this.model != null) { - json[r'model'] = this.model; - } else { - // json[r'model'] = null; - } - json[r'ocr'] = this.ocr; - if (this.page != null) { - json[r'page'] = this.page; - } else { - // json[r'page'] = null; - } - json[r'personIds'] = this.personIds; - if (this.rating != null) { - json[r'rating'] = this.rating; - } else { - // json[r'rating'] = null; - } - if (this.state != null) { - json[r'state'] = this.state; - } else { - // json[r'state'] = null; - } - if (this.tagIds != null) { - json[r'tagIds'] = this.tagIds; - } else { - // json[r'tagIds'] = null; - } - if (this.takenAfter != null) { - json[r'takenAfter'] = this.takenAfter!.toUtc().toIso8601String(); - } else { - // json[r'takenAfter'] = null; - } - if (this.takenBefore != null) { - json[r'takenBefore'] = this.takenBefore!.toUtc().toIso8601String(); - } else { - // json[r'takenBefore'] = null; - } - if (this.trashedAfter != null) { - json[r'trashedAfter'] = this.trashedAfter!.toUtc().toIso8601String(); - } else { - // json[r'trashedAfter'] = null; - } - if (this.trashedBefore != null) { - json[r'trashedBefore'] = this.trashedBefore!.toUtc().toIso8601String(); - } else { - // json[r'trashedBefore'] = null; - } - if (this.type != null) { - json[r'type'] = this.type; - } else { - // json[r'type'] = null; - } - if (this.updatedAfter != null) { - json[r'updatedAfter'] = this.updatedAfter!.toUtc().toIso8601String(); - } else { - // json[r'updatedAfter'] = null; - } - if (this.updatedBefore != null) { - json[r'updatedBefore'] = this.updatedBefore!.toUtc().toIso8601String(); - } else { - // json[r'updatedBefore'] = null; - } - if (this.visibility != null) { - json[r'visibility'] = this.visibility; - } else { - // json[r'visibility'] = null; - } - return json; - } - - /// Returns a new [OcrSearchDto] instance and imports its values from - /// [value] if it's a [Map], null otherwise. - // ignore: prefer_constructors_over_static_methods - static OcrSearchDto? fromJson(dynamic value) { - upgradeDto(value, "OcrSearchDto"); - if (value is Map) { - final json = value.cast(); - - return OcrSearchDto( - albumIds: json[r'albumIds'] is Iterable - ? (json[r'albumIds'] as Iterable).cast().toList(growable: false) - : const [], - city: mapValueOfType(json, r'city'), - country: mapValueOfType(json, r'country'), - createdAfter: mapDateTime(json, r'createdAfter', r''), - createdBefore: mapDateTime(json, r'createdBefore', r''), - deviceId: mapValueOfType(json, r'deviceId'), - isEncoded: mapValueOfType(json, r'isEncoded'), - isFavorite: mapValueOfType(json, r'isFavorite'), - isMotion: mapValueOfType(json, r'isMotion'), - isNotInAlbum: mapValueOfType(json, r'isNotInAlbum'), - isOffline: mapValueOfType(json, r'isOffline'), - lensModel: mapValueOfType(json, r'lensModel'), - libraryId: mapValueOfType(json, r'libraryId'), - make: mapValueOfType(json, r'make'), - model: mapValueOfType(json, r'model'), - ocr: mapValueOfType(json, r'ocr')!, - page: num.parse('${json[r'page']}'), - personIds: json[r'personIds'] is Iterable - ? (json[r'personIds'] as Iterable).cast().toList(growable: false) - : const [], - rating: num.parse('${json[r'rating']}'), - state: mapValueOfType(json, r'state'), - tagIds: json[r'tagIds'] is Iterable - ? (json[r'tagIds'] as Iterable).cast().toList(growable: false) - : const [], - takenAfter: mapDateTime(json, r'takenAfter', r''), - takenBefore: mapDateTime(json, r'takenBefore', r''), - 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''), - visibility: AssetVisibility.fromJson(json[r'visibility']), - ); - } - return null; - } - - static List listFromJson(dynamic json, {bool growable = false,}) { - final result = []; - if (json is List && json.isNotEmpty) { - for (final row in json) { - final value = OcrSearchDto.fromJson(row); - if (value != null) { - result.add(value); - } - } - } - return result.toList(growable: growable); - } - - static Map mapFromJson(dynamic json) { - final map = {}; - if (json is Map && json.isNotEmpty) { - json = json.cast(); // ignore: parameter_assignments - for (final entry in json.entries) { - final value = OcrSearchDto.fromJson(entry.value); - if (value != null) { - map[entry.key] = value; - } - } - } - return map; - } - - // maps a json object with a list of OcrSearchDto-objects as value to a dart map - static Map> mapListFromJson(dynamic json, {bool growable = false,}) { - final map = >{}; - if (json is Map && json.isNotEmpty) { - // ignore: parameter_assignments - json = json.cast(); - for (final entry in json.entries) { - map[entry.key] = OcrSearchDto.listFromJson(entry.value, growable: growable,); - } - } - return map; - } - - /// The list of required keys that must be present in a JSON. - static const requiredKeys = { - 'ocr', - }; -} - diff --git a/mobile/openapi/lib/model/random_search_dto.dart b/mobile/openapi/lib/model/random_search_dto.dart index 98cc715af4..96d670fd96 100644 --- a/mobile/openapi/lib/model/random_search_dto.dart +++ b/mobile/openapi/lib/model/random_search_dto.dart @@ -28,6 +28,7 @@ class RandomSearchDto { this.libraryId, this.make, this.model, + this.ocr, this.personIds = const [], this.rating, this.size, @@ -131,6 +132,14 @@ class RandomSearchDto { String? model; + /// + /// 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? ocr; + List personIds; /// Minimum value: -1 @@ -270,6 +279,7 @@ class RandomSearchDto { other.libraryId == libraryId && other.make == make && other.model == model && + other.ocr == ocr && _deepEquality.equals(other.personIds, personIds) && other.rating == rating && other.size == size && @@ -306,6 +316,7 @@ class RandomSearchDto { (libraryId == null ? 0 : libraryId!.hashCode) + (make == null ? 0 : make!.hashCode) + (model == null ? 0 : model!.hashCode) + + (ocr == null ? 0 : ocr!.hashCode) + (personIds.hashCode) + (rating == null ? 0 : rating!.hashCode) + (size == null ? 0 : size!.hashCode) + @@ -325,7 +336,7 @@ class RandomSearchDto { (withStacked == null ? 0 : withStacked!.hashCode); @override - String toString() => 'RandomSearchDto[albumIds=$albumIds, city=$city, country=$country, createdAfter=$createdAfter, createdBefore=$createdBefore, deviceId=$deviceId, isEncoded=$isEncoded, isFavorite=$isFavorite, isMotion=$isMotion, isNotInAlbum=$isNotInAlbum, isOffline=$isOffline, lensModel=$lensModel, libraryId=$libraryId, make=$make, model=$model, personIds=$personIds, rating=$rating, size=$size, state=$state, tagIds=$tagIds, takenAfter=$takenAfter, takenBefore=$takenBefore, trashedAfter=$trashedAfter, trashedBefore=$trashedBefore, type=$type, updatedAfter=$updatedAfter, updatedBefore=$updatedBefore, visibility=$visibility, withDeleted=$withDeleted, withExif=$withExif, withPeople=$withPeople, withStacked=$withStacked]'; + String toString() => 'RandomSearchDto[albumIds=$albumIds, city=$city, country=$country, createdAfter=$createdAfter, createdBefore=$createdBefore, deviceId=$deviceId, isEncoded=$isEncoded, isFavorite=$isFavorite, isMotion=$isMotion, isNotInAlbum=$isNotInAlbum, isOffline=$isOffline, lensModel=$lensModel, libraryId=$libraryId, make=$make, model=$model, ocr=$ocr, personIds=$personIds, rating=$rating, size=$size, state=$state, tagIds=$tagIds, takenAfter=$takenAfter, takenBefore=$takenBefore, trashedAfter=$trashedAfter, trashedBefore=$trashedBefore, type=$type, updatedAfter=$updatedAfter, updatedBefore=$updatedBefore, visibility=$visibility, withDeleted=$withDeleted, withExif=$withExif, withPeople=$withPeople, withStacked=$withStacked]'; Map toJson() { final json = {}; @@ -399,6 +410,11 @@ class RandomSearchDto { json[r'model'] = this.model; } else { // json[r'model'] = null; + } + if (this.ocr != null) { + json[r'ocr'] = this.ocr; + } else { + // json[r'ocr'] = null; } json[r'personIds'] = this.personIds; if (this.rating != null) { @@ -510,6 +526,7 @@ class RandomSearchDto { libraryId: mapValueOfType(json, r'libraryId'), make: mapValueOfType(json, r'make'), model: mapValueOfType(json, r'model'), + ocr: mapValueOfType(json, r'ocr'), personIds: json[r'personIds'] is Iterable ? (json[r'personIds'] as Iterable).cast().toList(growable: false) : const [], diff --git a/mobile/openapi/lib/model/smart_search_dto.dart b/mobile/openapi/lib/model/smart_search_dto.dart index 90902b9791..24f040a92b 100644 --- a/mobile/openapi/lib/model/smart_search_dto.dart +++ b/mobile/openapi/lib/model/smart_search_dto.dart @@ -29,6 +29,7 @@ class SmartSearchDto { this.libraryId, this.make, this.model, + this.ocr, this.page, this.personIds = const [], this.query, @@ -141,6 +142,14 @@ class SmartSearchDto { String? model; + /// + /// 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? ocr; + /// Minimum value: 1 /// /// Please note: This property should have been non-nullable! Since the specification file @@ -290,6 +299,7 @@ class SmartSearchDto { other.libraryId == libraryId && other.make == make && other.model == model && + other.ocr == ocr && other.page == page && _deepEquality.equals(other.personIds, personIds) && other.query == query && @@ -328,6 +338,7 @@ class SmartSearchDto { (libraryId == null ? 0 : libraryId!.hashCode) + (make == null ? 0 : make!.hashCode) + (model == null ? 0 : model!.hashCode) + + (ocr == null ? 0 : ocr!.hashCode) + (page == null ? 0 : page!.hashCode) + (personIds.hashCode) + (query == null ? 0 : query!.hashCode) + @@ -348,7 +359,7 @@ class SmartSearchDto { (withExif == null ? 0 : withExif!.hashCode); @override - String toString() => 'SmartSearchDto[albumIds=$albumIds, city=$city, country=$country, createdAfter=$createdAfter, createdBefore=$createdBefore, deviceId=$deviceId, isEncoded=$isEncoded, isFavorite=$isFavorite, isMotion=$isMotion, isNotInAlbum=$isNotInAlbum, isOffline=$isOffline, language=$language, lensModel=$lensModel, libraryId=$libraryId, make=$make, model=$model, page=$page, personIds=$personIds, query=$query, queryAssetId=$queryAssetId, rating=$rating, size=$size, state=$state, tagIds=$tagIds, takenAfter=$takenAfter, takenBefore=$takenBefore, trashedAfter=$trashedAfter, trashedBefore=$trashedBefore, type=$type, updatedAfter=$updatedAfter, updatedBefore=$updatedBefore, visibility=$visibility, withDeleted=$withDeleted, withExif=$withExif]'; + String toString() => 'SmartSearchDto[albumIds=$albumIds, city=$city, country=$country, createdAfter=$createdAfter, createdBefore=$createdBefore, deviceId=$deviceId, isEncoded=$isEncoded, isFavorite=$isFavorite, isMotion=$isMotion, isNotInAlbum=$isNotInAlbum, isOffline=$isOffline, language=$language, lensModel=$lensModel, libraryId=$libraryId, make=$make, model=$model, ocr=$ocr, page=$page, personIds=$personIds, query=$query, queryAssetId=$queryAssetId, rating=$rating, size=$size, state=$state, tagIds=$tagIds, takenAfter=$takenAfter, takenBefore=$takenBefore, trashedAfter=$trashedAfter, trashedBefore=$trashedBefore, type=$type, updatedAfter=$updatedAfter, updatedBefore=$updatedBefore, visibility=$visibility, withDeleted=$withDeleted, withExif=$withExif]'; Map toJson() { final json = {}; @@ -428,6 +439,11 @@ class SmartSearchDto { } else { // json[r'model'] = null; } + if (this.ocr != null) { + json[r'ocr'] = this.ocr; + } else { + // json[r'ocr'] = null; + } if (this.page != null) { json[r'page'] = this.page; } else { @@ -544,6 +560,7 @@ class SmartSearchDto { libraryId: mapValueOfType(json, r'libraryId'), make: mapValueOfType(json, r'make'), model: mapValueOfType(json, r'model'), + ocr: mapValueOfType(json, r'ocr'), page: num.parse('${json[r'page']}'), personIds: json[r'personIds'] is Iterable ? (json[r'personIds'] as Iterable).cast().toList(growable: false) diff --git a/mobile/openapi/lib/model/statistics_search_dto.dart b/mobile/openapi/lib/model/statistics_search_dto.dart index 73d80c9e36..e0965352e0 100644 --- a/mobile/openapi/lib/model/statistics_search_dto.dart +++ b/mobile/openapi/lib/model/statistics_search_dto.dart @@ -29,6 +29,7 @@ class StatisticsSearchDto { this.libraryId, this.make, this.model, + this.ocr, this.personIds = const [], this.rating, this.state, @@ -135,6 +136,14 @@ class StatisticsSearchDto { String? model; + /// + /// 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? ocr; + List personIds; /// Minimum value: -1 @@ -233,6 +242,7 @@ class StatisticsSearchDto { other.libraryId == libraryId && other.make == make && other.model == model && + other.ocr == ocr && _deepEquality.equals(other.personIds, personIds) && other.rating == rating && other.state == state && @@ -265,6 +275,7 @@ class StatisticsSearchDto { (libraryId == null ? 0 : libraryId!.hashCode) + (make == null ? 0 : make!.hashCode) + (model == null ? 0 : model!.hashCode) + + (ocr == null ? 0 : ocr!.hashCode) + (personIds.hashCode) + (rating == null ? 0 : rating!.hashCode) + (state == null ? 0 : state!.hashCode) + @@ -279,7 +290,7 @@ class StatisticsSearchDto { (visibility == null ? 0 : visibility!.hashCode); @override - String toString() => 'StatisticsSearchDto[albumIds=$albumIds, city=$city, country=$country, createdAfter=$createdAfter, createdBefore=$createdBefore, description=$description, deviceId=$deviceId, isEncoded=$isEncoded, isFavorite=$isFavorite, isMotion=$isMotion, isNotInAlbum=$isNotInAlbum, isOffline=$isOffline, lensModel=$lensModel, libraryId=$libraryId, make=$make, model=$model, personIds=$personIds, rating=$rating, state=$state, tagIds=$tagIds, takenAfter=$takenAfter, takenBefore=$takenBefore, trashedAfter=$trashedAfter, trashedBefore=$trashedBefore, type=$type, updatedAfter=$updatedAfter, updatedBefore=$updatedBefore, visibility=$visibility]'; + String toString() => 'StatisticsSearchDto[albumIds=$albumIds, city=$city, country=$country, createdAfter=$createdAfter, createdBefore=$createdBefore, description=$description, deviceId=$deviceId, isEncoded=$isEncoded, isFavorite=$isFavorite, isMotion=$isMotion, isNotInAlbum=$isNotInAlbum, isOffline=$isOffline, lensModel=$lensModel, libraryId=$libraryId, make=$make, model=$model, ocr=$ocr, personIds=$personIds, rating=$rating, state=$state, tagIds=$tagIds, takenAfter=$takenAfter, takenBefore=$takenBefore, trashedAfter=$trashedAfter, trashedBefore=$trashedBefore, type=$type, updatedAfter=$updatedAfter, updatedBefore=$updatedBefore, visibility=$visibility]'; Map toJson() { final json = {}; @@ -358,6 +369,11 @@ class StatisticsSearchDto { json[r'model'] = this.model; } else { // json[r'model'] = null; + } + if (this.ocr != null) { + json[r'ocr'] = this.ocr; + } else { + // json[r'ocr'] = null; } json[r'personIds'] = this.personIds; if (this.rating != null) { @@ -445,6 +461,7 @@ class StatisticsSearchDto { libraryId: mapValueOfType(json, r'libraryId'), make: mapValueOfType(json, r'make'), model: mapValueOfType(json, r'model'), + ocr: mapValueOfType(json, r'ocr'), personIds: json[r'personIds'] is Iterable ? (json[r'personIds'] as Iterable).cast().toList(growable: false) : const [], diff --git a/open-api/immich-openapi-specs.json b/open-api/immich-openapi-specs.json index 28125efdb3..7aac2025b0 100644 --- a/open-api/immich-openapi-specs.json +++ b/open-api/immich-openapi-specs.json @@ -5933,6 +5933,14 @@ "type": "string" } }, + { + "name": "ocr", + "required": false, + "in": "query", + "schema": { + "type": "string" + } + }, { "name": "personIds", "required": false, @@ -6151,48 +6159,6 @@ "description": "This endpoint requires the `asset.read` permission." } }, - "/search/ocr": { - "post": { - "operationId": "searchOcr", - "parameters": [], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/OcrSearchDto" - } - } - }, - "required": true - }, - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SearchResponseDto" - } - } - }, - "description": "" - } - }, - "security": [ - { - "bearer": [] - }, - { - "cookie": [] - }, - { - "api_key": [] - } - ], - "tags": [ - "Search" - ] - } - }, "/search/person": { "get": { "operationId": "searchPerson", @@ -12604,6 +12570,9 @@ "nullable": true, "type": "string" }, + "ocr": { + "type": "string" + }, "order": { "allOf": [ { @@ -12918,176 +12887,32 @@ "enabled": { "type": "boolean" }, - "minDetectionBoxScore": { - "format": "double", - "maximum": 1, - "minimum": 0, - "type": "number" + "maxResolution": { + "minimum": 1, + "type": "integer" }, "minDetectionScore": { "format": "double", "maximum": 1, - "minimum": 0, + "minimum": 0.1, "type": "number" }, "minRecognitionScore": { "format": "double", "maximum": 1, - "minimum": 0, + "minimum": 0.1, "type": "number" }, "modelName": { "type": "string" - }, - "orientationClassifyEnabled": { - "type": "boolean" - }, - "unwarpingEnabled": { - "type": "boolean" } }, "required": [ "enabled", - "minDetectionBoxScore", + "maxResolution", "minDetectionScore", "minRecognitionScore", - "modelName", - "orientationClassifyEnabled", - "unwarpingEnabled" - ], - "type": "object" - }, - "OcrSearchDto": { - "properties": { - "albumIds": { - "items": { - "format": "uuid", - "type": "string" - }, - "type": "array" - }, - "city": { - "nullable": true, - "type": "string" - }, - "country": { - "nullable": true, - "type": "string" - }, - "createdAfter": { - "format": "date-time", - "type": "string" - }, - "createdBefore": { - "format": "date-time", - "type": "string" - }, - "deviceId": { - "type": "string" - }, - "isEncoded": { - "type": "boolean" - }, - "isFavorite": { - "type": "boolean" - }, - "isMotion": { - "type": "boolean" - }, - "isNotInAlbum": { - "type": "boolean" - }, - "isOffline": { - "type": "boolean" - }, - "lensModel": { - "nullable": true, - "type": "string" - }, - "libraryId": { - "format": "uuid", - "nullable": true, - "type": "string" - }, - "make": { - "type": "string" - }, - "model": { - "nullable": true, - "type": "string" - }, - "ocr": { - "type": "string" - }, - "page": { - "minimum": 1, - "type": "number" - }, - "personIds": { - "items": { - "format": "uuid", - "type": "string" - }, - "type": "array" - }, - "rating": { - "maximum": 5, - "minimum": -1, - "type": "number" - }, - "state": { - "nullable": true, - "type": "string" - }, - "tagIds": { - "items": { - "format": "uuid", - "type": "string" - }, - "nullable": true, - "type": "array" - }, - "takenAfter": { - "format": "date-time", - "type": "string" - }, - "takenBefore": { - "format": "date-time", - "type": "string" - }, - "trashedAfter": { - "format": "date-time", - "type": "string" - }, - "trashedBefore": { - "format": "date-time", - "type": "string" - }, - "type": { - "allOf": [ - { - "$ref": "#/components/schemas/AssetTypeEnum" - } - ] - }, - "updatedAfter": { - "format": "date-time", - "type": "string" - }, - "updatedBefore": { - "format": "date-time", - "type": "string" - }, - "visibility": { - "allOf": [ - { - "$ref": "#/components/schemas/AssetVisibility" - } - ] - } - }, - "required": [ - "ocr" + "modelName" ], "type": "object" }, @@ -13760,6 +13585,9 @@ "nullable": true, "type": "string" }, + "ocr": { + "type": "string" + }, "personIds": { "items": { "format": "uuid", @@ -14856,6 +14684,9 @@ "nullable": true, "type": "string" }, + "ocr": { + "type": "string" + }, "page": { "minimum": 1, "type": "number" @@ -15061,6 +14892,9 @@ "nullable": true, "type": "string" }, + "ocr": { + "type": "string" + }, "personIds": { "items": { "format": "uuid", diff --git a/open-api/typescript-sdk/src/fetch-client.ts b/open-api/typescript-sdk/src/fetch-client.ts index 35b889cc4d..c50efb7d38 100644 --- a/open-api/typescript-sdk/src/fetch-client.ts +++ b/open-api/typescript-sdk/src/fetch-client.ts @@ -909,6 +909,7 @@ export type MetadataSearchDto = { libraryId?: string | null; make?: string; model?: string | null; + ocr?: string; order?: AssetOrder; originalFileName?: string; originalPath?: string; @@ -958,37 +959,6 @@ export type SearchResponseDto = { albums: SearchAlbumResponseDto; assets: SearchAssetResponseDto; }; -export type OcrSearchDto = { - albumIds?: string[]; - city?: string | null; - country?: string | null; - createdAfter?: string; - createdBefore?: string; - deviceId?: string; - isEncoded?: boolean; - isFavorite?: boolean; - isMotion?: boolean; - isNotInAlbum?: boolean; - isOffline?: boolean; - lensModel?: string | null; - libraryId?: string | null; - make?: string; - model?: string | null; - ocr: string; - page?: number; - personIds?: string[]; - rating?: number; - state?: string | null; - tagIds?: string[] | null; - takenAfter?: string; - takenBefore?: string; - trashedAfter?: string; - trashedBefore?: string; - "type"?: AssetTypeEnum; - updatedAfter?: string; - updatedBefore?: string; - visibility?: AssetVisibility; -}; export type PlacesResponseDto = { admin1name?: string; admin2name?: string; @@ -1012,6 +982,7 @@ export type RandomSearchDto = { libraryId?: string | null; make?: string; model?: string | null; + ocr?: string; personIds?: string[]; rating?: number; size?: number; @@ -1047,6 +1018,7 @@ export type SmartSearchDto = { libraryId?: string | null; make?: string; model?: string | null; + ocr?: string; page?: number; personIds?: string[]; query?: string; @@ -1083,6 +1055,7 @@ export type StatisticsSearchDto = { libraryId?: string | null; make?: string; model?: string | null; + ocr?: string; personIds?: string[]; rating?: number; state?: string | null; @@ -1434,7 +1407,9 @@ export type FacialRecognitionConfig = { }; export type OcrConfig = { enabled: boolean; - minScore: number; + maxResolution: number; + minDetectionScore: number; + minRecognitionScore: number; modelName: string; }; export type SystemConfigMachineLearningDto = { @@ -3409,7 +3384,7 @@ export function getExploreData(opts?: Oazapfts.RequestOpts) { /** * This endpoint requires the `asset.read` permission. */ -export function searchLargeAssets({ albumIds, city, country, createdAfter, createdBefore, deviceId, isEncoded, isFavorite, isMotion, isNotInAlbum, isOffline, lensModel, libraryId, make, minFileSize, model, personIds, rating, size, state, tagIds, takenAfter, takenBefore, trashedAfter, trashedBefore, $type, updatedAfter, updatedBefore, visibility, withDeleted, withExif }: { +export function searchLargeAssets({ albumIds, city, country, createdAfter, createdBefore, deviceId, isEncoded, isFavorite, isMotion, isNotInAlbum, isOffline, lensModel, libraryId, make, minFileSize, model, ocr, personIds, rating, size, state, tagIds, takenAfter, takenBefore, trashedAfter, trashedBefore, $type, updatedAfter, updatedBefore, visibility, withDeleted, withExif }: { albumIds?: string[]; city?: string | null; country?: string | null; @@ -3426,6 +3401,7 @@ export function searchLargeAssets({ albumIds, city, country, createdAfter, creat make?: string; minFileSize?: number; model?: string | null; + ocr?: string; personIds?: string[]; rating?: number; size?: number; @@ -3462,6 +3438,7 @@ export function searchLargeAssets({ albumIds, city, country, createdAfter, creat make, minFileSize, model, + ocr, personIds, rating, size, @@ -3497,18 +3474,6 @@ export function searchAssets({ metadataSearchDto }: { body: metadataSearchDto }))); } -export function searchOcr({ ocrSearchDto }: { - ocrSearchDto: OcrSearchDto; -}, opts?: Oazapfts.RequestOpts) { - return oazapfts.ok(oazapfts.fetchJson<{ - status: 200; - data: SearchResponseDto; - }>("/search/ocr", oazapfts.json({ - ...opts, - method: "POST", - body: ocrSearchDto - }))); -} /** * This endpoint requires the `person.read` permission. */ diff --git a/server/src/config.ts b/server/src/config.ts index 64cd968db1..38d6634727 100644 --- a/server/src/config.ts +++ b/server/src/config.ts @@ -253,8 +253,8 @@ export const defaults = Object.freeze({ ocr: { enabled: true, modelName: 'PP-OCRv5_server', - minDetectionScore: 0.3, - minRecognitionScore: 0.0, + minDetectionScore: 0.5, + minRecognitionScore: 0.5, maxResolution: 1440, }, }, diff --git a/server/src/dtos/search.dto.ts b/server/src/dtos/search.dto.ts index 8eca3bdbe1..21265516c6 100644 --- a/server/src/dtos/search.dto.ts +++ b/server/src/dtos/search.dto.ts @@ -101,6 +101,11 @@ class BaseSearchDto { @Max(5) @Min(-1) rating?: number; + + @IsString() + @IsNotEmpty() + @Optional() + ocr?: string; } class BaseSearchWithResultsDto extends BaseSearchDto { @@ -218,18 +223,6 @@ export class SmartSearchDto extends BaseSearchWithResultsDto { page?: number; } -export class OcrSearchDto extends BaseSearchDto { - @IsString() - @IsNotEmpty() - ocr!: string; - - @IsInt() - @Min(1) - @Type(() => Number) - @Optional() - page?: number; -} - export class SearchPlacesDto { @IsString() @IsNotEmpty() diff --git a/server/src/repositories/asset-job.repository.ts b/server/src/repositories/asset-job.repository.ts index 18827aeb07..d421d88c52 100644 --- a/server/src/repositories/asset-job.repository.ts +++ b/server/src/repositories/asset-job.repository.ts @@ -196,9 +196,9 @@ export class AssetJobRepository { @GenerateSql({ params: [DummyValue.UUID] }) getForOcr(id: string) { return this.db - .selectFrom('assets') - .select((eb) => ['assets.visibility', withFile(eb, AssetFileType.PREVIEW).as('previewFile')]) - .where('assets.id', '=', id) + .selectFrom('asset') + .select((eb) => ['asset.visibility', withFile(eb, AssetFileType.Preview).as('previewFile')]) + .where('asset.id', '=', id) .executeTakeFirst(); } @@ -361,15 +361,15 @@ export class AssetJobRepository { @GenerateSql({ params: [], stream: true }) streamForOcrJob(force?: boolean) { return this.db - .selectFrom('assets') - .select(['assets.id']) + .selectFrom('asset') + .select(['asset.id']) .$if(!force, (qb) => qb - .innerJoin('asset_job_status', 'asset_job_status.assetId', 'assets.id') + .innerJoin('asset_job_status', 'asset_job_status.assetId', 'asset.id') .where('asset_job_status.ocrAt', 'is', null), ) - .where('assets.deletedAt', 'is', null) - .where('assets.visibility', '!=', AssetVisibility.HIDDEN) + .where('asset.deletedAt', 'is', null) + .where('asset.visibility', '!=', AssetVisibility.Hidden) .stream(); } diff --git a/server/src/repositories/ocr.repository.ts b/server/src/repositories/ocr.repository.ts index 01463a7fb3..d858af6c3b 100644 --- a/server/src/repositories/ocr.repository.ts +++ b/server/src/repositories/ocr.repository.ts @@ -1,8 +1,9 @@ import { Injectable } from '@nestjs/common'; import { Insertable, Kysely, sql } from 'kysely'; import { InjectKysely } from 'nestjs-kysely'; -import { AssetOcr, DB } from 'src/db'; import { DummyValue, GenerateSql } from 'src/decorators'; +import { DB } from 'src/schema'; +import { AssetOcrTable } from 'src/schema/tables/asset-ocr.table'; @Injectable() export class OcrRepository { @@ -46,7 +47,7 @@ export class OcrRepository { ], ], }) - upsert(assetId: string, ocrDataList: Insertable[]) { + upsert(assetId: string, ocrDataList: Insertable[]) { let query = this.db.with('deleted_ocr', (db) => db.deleteFrom('asset_ocr').where('assetId', '=', assetId)); if (ocrDataList.length > 0) { const searchText = ocrDataList.map((item) => item.text.trim()).join(' '); diff --git a/server/src/schema/index.ts b/server/src/schema/index.ts index 337a18e1b7..7f4bdbeed3 100644 --- a/server/src/schema/index.ts +++ b/server/src/schema/index.ts @@ -178,6 +178,8 @@ export interface DB { asset_metadata: AssetMetadataTable; asset_metadata_audit: AssetMetadataAuditTable; asset_job_status: AssetJobStatusTable; + asset_ocr: AssetOcrTable; + ocr_search: OcrSearchTable; audit: AuditTable; diff --git a/server/src/schema/tables/asset-ocr.table.ts b/server/src/schema/tables/asset-ocr.table.ts index b29136ccd7..df2a3da866 100644 --- a/server/src/schema/tables/asset-ocr.table.ts +++ b/server/src/schema/tables/asset-ocr.table.ts @@ -1,10 +1,10 @@ import { AssetTable } from 'src/schema/tables/asset.table'; -import { Column, ForeignKeyColumn, PrimaryGeneratedColumn, Table } from 'src/sql-tools'; +import { Column, ForeignKeyColumn, Generated, PrimaryGeneratedColumn, Table } from 'src/sql-tools'; @Table('asset_ocr') export class AssetOcrTable { @PrimaryGeneratedColumn() - id!: string; + id!: Generated; @ForeignKeyColumn(() => AssetTable, { onDelete: 'CASCADE', onUpdate: 'CASCADE' }) assetId!: string; diff --git a/server/src/services/job.service.ts b/server/src/services/job.service.ts index 47251b527a..81a14f41a8 100644 --- a/server/src/services/job.service.ts +++ b/server/src/services/job.service.ts @@ -308,7 +308,6 @@ export class JobService extends BaseService { if (config.nightlyTasks.clusterNewFaces) { jobs.push({ name: JobName.FacialRecognitionQueueAll, data: { force: false, nightly: true } }); - { name: JobName.QUEUE_OCR, data: { force: false, nightly: true } }, } await this.jobRepository.queueAll(jobs); diff --git a/server/src/services/ocr.service.ts b/server/src/services/ocr.service.ts index 969becbe97..b8a41b4e6c 100644 --- a/server/src/services/ocr.service.ts +++ b/server/src/services/ocr.service.ts @@ -13,7 +13,7 @@ export class OcrService extends BaseService { async handleQueueOcr({ force, nightly }: JobOf): Promise { const { machineLearning } = await this.getConfig({ withCache: false }); if (!isOcrEnabled(machineLearning)) { - return JobStatus.SKIPPED; + return JobStatus.Skipped; } if (force) { @@ -33,23 +33,23 @@ export class OcrService extends BaseService { } await this.jobRepository.queueAll(jobs); - return JobStatus.SUCCESS; + return JobStatus.Success; } @OnJob({ name: JobName.OCR, queue: QueueName.OCR }) async handleOcr({ id }: JobOf): Promise { const { machineLearning } = await this.getConfig({ withCache: true }); if (!isOcrEnabled(machineLearning)) { - return JobStatus.SKIPPED; + return JobStatus.Skipped; } const asset = await this.assetJobRepository.getForOcr(id); if (!asset || !asset.previewFile) { - return JobStatus.FAILED; + return JobStatus.Failed; } - if (asset.visibility === AssetVisibility.HIDDEN) { - return JobStatus.SKIPPED; + if (asset.visibility === AssetVisibility.Hidden) { + return JobStatus.Skipped; } const ocrResults = await this.machineLearningRepository.ocr( @@ -63,7 +63,7 @@ export class OcrService extends BaseService { await this.assetRepository.upsertJobStatus({ assetId: id, ocrAt: new Date() }); this.logger.debug(`Processed ${ocrResults.text.length} OCR result(s) for ${id}`); - return JobStatus.SUCCESS; + return JobStatus.Success; } parseOcrResults(id: string, ocrResults: OCR) { diff --git a/server/src/utils/database.ts b/server/src/utils/database.ts index 36ad2a7003..af94cdef2e 100644 --- a/server/src/utils/database.ts +++ b/server/src/utils/database.ts @@ -390,7 +390,7 @@ export function searchAssetBuilder(kysely: Kysely, options: AssetSearchBuild ) .$if(!!options.ocr, (qb) => qb - .innerJoin('ocr_search', 'assets.id', 'ocr_search.assetId') + .innerJoin('ocr_search', 'asset.id', 'ocr_search.assetId') .where(() => sql`f_unaccent(ocr_search.text) %>> f_unaccent(${options.ocr!})`), ) .$if(!!options.type, (qb) => qb.where('asset.type', '=', options.type!)) diff --git a/web/src/lib/components/shared-components/search-bar/search-bar.svelte b/web/src/lib/components/shared-components/search-bar/search-bar.svelte index 82ae315e62..c1b3da70e8 100644 --- a/web/src/lib/components/shared-components/search-bar/search-bar.svelte +++ b/web/src/lib/components/shared-components/search-bar/search-bar.svelte @@ -39,7 +39,7 @@ searchStore.isSearchEnabled = false; }); - const handleSearch = async (payload: SmartSearchDto | MetadataSearchDto | OcrSearchDto) => { + const handleSearch = async (payload: SmartSearchDto | MetadataSearchDto) => { const params = getMetadataSearchQuery(payload); closeDropdown();