feat: add image aspect ratio & resolution filter

pull/28739/head
knom 2026-05-27 10:07:55 +00:00
parent 2382894fa2
commit 517beaf2e6
17 changed files with 1176 additions and 8 deletions

View File

@ -557,6 +557,9 @@
"are_these_the_same_person": "Are these the same person?",
"are_you_sure_to_do_this": "Are you sure you want to do this?",
"array_field_not_fully_supported": "Array fields require manual JSON editing",
"aspect_ratio": "Aspect ratio",
"aspect_ratio_height": "Aspect ratio height",
"aspect_ratio_width": "Aspect ratio width",
"asset_action_delete_err_read_only": "Cannot delete read only asset(s), skipping",
"asset_action_share_err_offline": "Cannot fetch offline asset(s), skipping",
"asset_added_to_album": "Added to album",
@ -1326,6 +1329,7 @@
"image_alt_text_date_place_2_people": "{isVideo, select, true {Video} other {Image}} taken in {city}, {country} with {person1} and {person2} on {date}",
"image_alt_text_date_place_3_people": "{isVideo, select, true {Video} other {Image}} taken in {city}, {country} with {person1}, {person2}, and {person3} on {date}",
"image_alt_text_date_place_4_or_more_people": "{isVideo, select, true {Video} other {Image}} taken in {city}, {country} with {person1}, {person2}, and {additionalCount, number} others on {date}",
"image_properties": "Image properties",
"image_saved_successfully": "Image saved",
"image_viewer_page_state_provider_download_started": "Download Started",
"image_viewer_page_state_provider_download_success": "Download Success",
@ -1584,7 +1588,13 @@
"merge_people_prompt": "Do you want to merge these people? This action is irreversible.",
"merge_people_successfully": "Merge people successfully",
"merged_people_count": "Merged {count, plural, one {# person} other {# people}}",
"max_aspect_ratio": "Max aspect ratio",
"max_height": "Max height",
"max_width": "Max width",
"minimize": "Minimize",
"min_aspect_ratio": "Min aspect ratio",
"min_height": "Min height",
"min_width": "Min width",
"minute": "Minute",
"minutes": "Minutes",
"mirror_horizontal": "Horizontal",

View File

@ -397,9 +397,27 @@ class SearchApi {
/// * [String] make:
/// Filter by camera make
///
/// * [num] maxAspectRatio:
/// Filter by maximum aspect ratio (width/height)
///
/// * [int] maxHeight:
/// Filter by maximum image height
///
/// * [int] maxWidth:
/// Filter by maximum image width
///
/// * [num] minAspectRatio:
/// Filter by minimum aspect ratio (width/height)
///
/// * [int] minFileSize:
/// Minimum file size in bytes
///
/// * [int] minHeight:
/// Filter by minimum image height
///
/// * [int] minWidth:
/// Filter by minimum image width
///
/// * [String] model:
/// Filter by camera model
///
@ -448,7 +466,7 @@ class SearchApi {
///
/// * [bool] withExif:
/// Include EXIF data in response
Future<Response> searchLargeAssetsWithHttpInfo({ List<String>? albumIds, String? city, String? country, DateTime? createdAfter, DateTime? createdBefore, bool? isEncoded, bool? isFavorite, bool? isMotion, bool? isNotInAlbum, bool? isOffline, String? lensModel, String? libraryId, String? make, int? minFileSize, String? model, String? ocr, List<String>? personIds, int? rating, int? size, String? state, List<String>? tagIds, DateTime? takenAfter, DateTime? takenBefore, DateTime? trashedAfter, DateTime? trashedBefore, AssetTypeEnum? type, DateTime? updatedAfter, DateTime? updatedBefore, AssetVisibility? visibility, bool? withDeleted, bool? withExif, Future<void>? abortTrigger, }) async {
Future<Response> searchLargeAssetsWithHttpInfo({ List<String>? albumIds, String? city, String? country, DateTime? createdAfter, DateTime? createdBefore, bool? isEncoded, bool? isFavorite, bool? isMotion, bool? isNotInAlbum, bool? isOffline, String? lensModel, String? libraryId, String? make, num? maxAspectRatio, int? maxHeight, int? maxWidth, num? minAspectRatio, int? minFileSize, int? minHeight, int? minWidth, String? model, String? ocr, List<String>? personIds, int? rating, int? size, String? state, List<String>? tagIds, DateTime? takenAfter, DateTime? takenBefore, DateTime? trashedAfter, DateTime? trashedBefore, AssetTypeEnum? type, DateTime? updatedAfter, DateTime? updatedBefore, AssetVisibility? visibility, bool? withDeleted, bool? withExif, Future<void>? abortTrigger, }) async {
// ignore: prefer_const_declarations
final apiPath = r'/search/large-assets';
@ -498,9 +516,27 @@ class SearchApi {
if (make != null) {
queryParams.addAll(_queryParams('', 'make', make));
}
if (maxAspectRatio != null) {
queryParams.addAll(_queryParams('', 'maxAspectRatio', maxAspectRatio));
}
if (maxHeight != null) {
queryParams.addAll(_queryParams('', 'maxHeight', maxHeight));
}
if (maxWidth != null) {
queryParams.addAll(_queryParams('', 'maxWidth', maxWidth));
}
if (minAspectRatio != null) {
queryParams.addAll(_queryParams('', 'minAspectRatio', minAspectRatio));
}
if (minFileSize != null) {
queryParams.addAll(_queryParams('', 'minFileSize', minFileSize));
}
if (minHeight != null) {
queryParams.addAll(_queryParams('', 'minHeight', minHeight));
}
if (minWidth != null) {
queryParams.addAll(_queryParams('', 'minWidth', minWidth));
}
if (model != null) {
queryParams.addAll(_queryParams('', 'model', model));
}
@ -613,9 +649,27 @@ class SearchApi {
/// * [String] make:
/// Filter by camera make
///
/// * [num] maxAspectRatio:
/// Filter by maximum aspect ratio (width/height)
///
/// * [int] maxHeight:
/// Filter by maximum image height
///
/// * [int] maxWidth:
/// Filter by maximum image width
///
/// * [num] minAspectRatio:
/// Filter by minimum aspect ratio (width/height)
///
/// * [int] minFileSize:
/// Minimum file size in bytes
///
/// * [int] minHeight:
/// Filter by minimum image height
///
/// * [int] minWidth:
/// Filter by minimum image width
///
/// * [String] model:
/// Filter by camera model
///
@ -664,8 +718,8 @@ class SearchApi {
///
/// * [bool] withExif:
/// Include EXIF data in response
Future<List<AssetResponseDto>?> searchLargeAssets({ List<String>? albumIds, String? city, String? country, DateTime? createdAfter, DateTime? createdBefore, bool? isEncoded, bool? isFavorite, bool? isMotion, bool? isNotInAlbum, bool? isOffline, String? lensModel, String? libraryId, String? make, int? minFileSize, String? model, String? ocr, List<String>? personIds, int? rating, int? size, String? state, List<String>? tagIds, DateTime? takenAfter, DateTime? takenBefore, DateTime? trashedAfter, DateTime? trashedBefore, AssetTypeEnum? type, DateTime? updatedAfter, DateTime? updatedBefore, AssetVisibility? visibility, bool? withDeleted, bool? withExif, Future<void>? abortTrigger, }) async {
final response = await searchLargeAssetsWithHttpInfo(albumIds: albumIds, city: city, country: country, createdAfter: createdAfter, createdBefore: createdBefore, 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, abortTrigger: abortTrigger,);
Future<List<AssetResponseDto>?> searchLargeAssets({ List<String>? albumIds, String? city, String? country, DateTime? createdAfter, DateTime? createdBefore, bool? isEncoded, bool? isFavorite, bool? isMotion, bool? isNotInAlbum, bool? isOffline, String? lensModel, String? libraryId, String? make, num? maxAspectRatio, int? maxHeight, int? maxWidth, num? minAspectRatio, int? minFileSize, int? minHeight, int? minWidth, String? model, String? ocr, List<String>? personIds, int? rating, int? size, String? state, List<String>? tagIds, DateTime? takenAfter, DateTime? takenBefore, DateTime? trashedAfter, DateTime? trashedBefore, AssetTypeEnum? type, DateTime? updatedAfter, DateTime? updatedBefore, AssetVisibility? visibility, bool? withDeleted, bool? withExif, Future<void>? abortTrigger, }) async {
final response = await searchLargeAssetsWithHttpInfo(albumIds: albumIds, city: city, country: country, createdAfter: createdAfter, createdBefore: createdBefore, isEncoded: isEncoded, isFavorite: isFavorite, isMotion: isMotion, isNotInAlbum: isNotInAlbum, isOffline: isOffline, lensModel: lensModel, libraryId: libraryId, make: make, maxAspectRatio: maxAspectRatio, maxHeight: maxHeight, maxWidth: maxWidth, minAspectRatio: minAspectRatio, minFileSize: minFileSize, minHeight: minHeight, minWidth: minWidth, 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, abortTrigger: abortTrigger,);
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}

View File

@ -30,6 +30,12 @@ class MetadataSearchDto {
this.lensModel,
this.libraryId,
this.make,
this.maxAspectRatio,
this.maxHeight,
this.maxWidth,
this.minAspectRatio,
this.minHeight,
this.minWidth,
this.model,
this.ocr,
this.order,
@ -174,6 +180,76 @@ class MetadataSearchDto {
/// Filter by camera make
String? make;
/// Filter by maximum aspect ratio (width/height)
///
/// Minimum value: 0
///
/// 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? maxAspectRatio;
/// Filter by maximum image height
///
/// Minimum value: 1
/// Maximum value: 9007199254740991
///
/// 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.
///
int? maxHeight;
/// Filter by maximum image width
///
/// Minimum value: 1
/// Maximum value: 9007199254740991
///
/// 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.
///
int? maxWidth;
/// Filter by minimum aspect ratio (width/height)
///
/// Minimum value: 0
///
/// 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? minAspectRatio;
/// Filter by minimum image height
///
/// Minimum value: 1
/// Maximum value: 9007199254740991
///
/// 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.
///
int? minHeight;
/// Filter by minimum image width
///
/// Minimum value: 1
/// Maximum value: 9007199254740991
///
/// 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.
///
int? minWidth;
/// Filter by camera model
String? model;
@ -394,6 +470,12 @@ class MetadataSearchDto {
other.lensModel == lensModel &&
other.libraryId == libraryId &&
other.make == make &&
other.maxAspectRatio == maxAspectRatio &&
other.maxHeight == maxHeight &&
other.maxWidth == maxWidth &&
other.minAspectRatio == minAspectRatio &&
other.minHeight == minHeight &&
other.minWidth == minWidth &&
other.model == model &&
other.ocr == ocr &&
other.order == order &&
@ -440,6 +522,12 @@ class MetadataSearchDto {
(lensModel == null ? 0 : lensModel!.hashCode) +
(libraryId == null ? 0 : libraryId!.hashCode) +
(make == null ? 0 : make!.hashCode) +
(maxAspectRatio == null ? 0 : maxAspectRatio!.hashCode) +
(maxHeight == null ? 0 : maxHeight!.hashCode) +
(maxWidth == null ? 0 : maxWidth!.hashCode) +
(minAspectRatio == null ? 0 : minAspectRatio!.hashCode) +
(minHeight == null ? 0 : minHeight!.hashCode) +
(minWidth == null ? 0 : minWidth!.hashCode) +
(model == null ? 0 : model!.hashCode) +
(ocr == null ? 0 : ocr!.hashCode) +
(order == null ? 0 : order!.hashCode) +
@ -467,7 +555,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, 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]';
String toString() => 'MetadataSearchDto[albumIds=$albumIds, checksum=$checksum, city=$city, country=$country, createdAfter=$createdAfter, createdBefore=$createdBefore, description=$description, encodedVideoPath=$encodedVideoPath, id=$id, isEncoded=$isEncoded, isFavorite=$isFavorite, isMotion=$isMotion, isNotInAlbum=$isNotInAlbum, isOffline=$isOffline, lensModel=$lensModel, libraryId=$libraryId, make=$make, maxAspectRatio=$maxAspectRatio, maxHeight=$maxHeight, maxWidth=$maxWidth, minAspectRatio=$minAspectRatio, minHeight=$minHeight, minWidth=$minWidth, 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<String, dynamic> toJson() {
final json = <String, dynamic>{};
@ -556,6 +644,36 @@ class MetadataSearchDto {
} else {
// json[r'make'] = null;
}
if (this.maxAspectRatio != null) {
json[r'maxAspectRatio'] = this.maxAspectRatio;
} else {
// json[r'maxAspectRatio'] = null;
}
if (this.maxHeight != null) {
json[r'maxHeight'] = this.maxHeight;
} else {
// json[r'maxHeight'] = null;
}
if (this.maxWidth != null) {
json[r'maxWidth'] = this.maxWidth;
} else {
// json[r'maxWidth'] = null;
}
if (this.minAspectRatio != null) {
json[r'minAspectRatio'] = this.minAspectRatio;
} else {
// json[r'minAspectRatio'] = null;
}
if (this.minHeight != null) {
json[r'minHeight'] = this.minHeight;
} else {
// json[r'minHeight'] = null;
}
if (this.minWidth != null) {
json[r'minWidth'] = this.minWidth;
} else {
// json[r'minWidth'] = null;
}
if (this.model != null) {
json[r'model'] = this.model;
} else {
@ -720,6 +838,12 @@ class MetadataSearchDto {
lensModel: mapValueOfType<String>(json, r'lensModel'),
libraryId: mapValueOfType<String>(json, r'libraryId'),
make: mapValueOfType<String>(json, r'make'),
maxAspectRatio: num.parse('${json[r'maxAspectRatio']}'),
maxHeight: mapValueOfType<int>(json, r'maxHeight'),
maxWidth: mapValueOfType<int>(json, r'maxWidth'),
minAspectRatio: num.parse('${json[r'minAspectRatio']}'),
minHeight: mapValueOfType<int>(json, r'minHeight'),
minWidth: mapValueOfType<int>(json, r'minWidth'),
model: mapValueOfType<String>(json, r'model'),
ocr: mapValueOfType<String>(json, r'ocr'),
order: AssetOrder.fromJson(json[r'order']),

View File

@ -26,6 +26,12 @@ class RandomSearchDto {
this.lensModel,
this.libraryId,
this.make,
this.maxAspectRatio,
this.maxHeight,
this.maxWidth,
this.minAspectRatio,
this.minHeight,
this.minWidth,
this.model,
this.ocr,
this.personIds = const [],
@ -128,6 +134,76 @@ class RandomSearchDto {
/// Filter by camera make
String? make;
/// Filter by maximum aspect ratio (width/height)
///
/// Minimum value: 0
///
/// 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? maxAspectRatio;
/// Filter by maximum image height
///
/// Minimum value: 1
/// Maximum value: 9007199254740991
///
/// 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.
///
int? maxHeight;
/// Filter by maximum image width
///
/// Minimum value: 1
/// Maximum value: 9007199254740991
///
/// 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.
///
int? maxWidth;
/// Filter by minimum aspect ratio (width/height)
///
/// Minimum value: 0
///
/// 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? minAspectRatio;
/// Filter by minimum image height
///
/// Minimum value: 1
/// Maximum value: 9007199254740991
///
/// 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.
///
int? minHeight;
/// Filter by minimum image width
///
/// Minimum value: 1
/// Maximum value: 9007199254740991
///
/// 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.
///
int? minWidth;
/// Filter by camera model
String? model;
@ -288,6 +364,12 @@ class RandomSearchDto {
other.lensModel == lensModel &&
other.libraryId == libraryId &&
other.make == make &&
other.maxAspectRatio == maxAspectRatio &&
other.maxHeight == maxHeight &&
other.maxWidth == maxWidth &&
other.minAspectRatio == minAspectRatio &&
other.minHeight == minHeight &&
other.minWidth == minWidth &&
other.model == model &&
other.ocr == ocr &&
_deepEquality.equals(other.personIds, personIds) &&
@ -324,6 +406,12 @@ class RandomSearchDto {
(lensModel == null ? 0 : lensModel!.hashCode) +
(libraryId == null ? 0 : libraryId!.hashCode) +
(make == null ? 0 : make!.hashCode) +
(maxAspectRatio == null ? 0 : maxAspectRatio!.hashCode) +
(maxHeight == null ? 0 : maxHeight!.hashCode) +
(maxWidth == null ? 0 : maxWidth!.hashCode) +
(minAspectRatio == null ? 0 : minAspectRatio!.hashCode) +
(minHeight == null ? 0 : minHeight!.hashCode) +
(minWidth == null ? 0 : minWidth!.hashCode) +
(model == null ? 0 : model!.hashCode) +
(ocr == null ? 0 : ocr!.hashCode) +
(personIds.hashCode) +
@ -345,7 +433,7 @@ class RandomSearchDto {
(withStacked == null ? 0 : withStacked!.hashCode);
@override
String toString() => 'RandomSearchDto[albumIds=$albumIds, city=$city, country=$country, createdAfter=$createdAfter, createdBefore=$createdBefore, 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]';
String toString() => 'RandomSearchDto[albumIds=$albumIds, city=$city, country=$country, createdAfter=$createdAfter, createdBefore=$createdBefore, isEncoded=$isEncoded, isFavorite=$isFavorite, isMotion=$isMotion, isNotInAlbum=$isNotInAlbum, isOffline=$isOffline, lensModel=$lensModel, libraryId=$libraryId, make=$make, maxAspectRatio=$maxAspectRatio, maxHeight=$maxHeight, maxWidth=$maxWidth, minAspectRatio=$minAspectRatio, minHeight=$minHeight, minWidth=$minWidth, 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<String, dynamic> toJson() {
final json = <String, dynamic>{};
@ -414,6 +502,36 @@ class RandomSearchDto {
} else {
// json[r'make'] = null;
}
if (this.maxAspectRatio != null) {
json[r'maxAspectRatio'] = this.maxAspectRatio;
} else {
// json[r'maxAspectRatio'] = null;
}
if (this.maxHeight != null) {
json[r'maxHeight'] = this.maxHeight;
} else {
// json[r'maxHeight'] = null;
}
if (this.maxWidth != null) {
json[r'maxWidth'] = this.maxWidth;
} else {
// json[r'maxWidth'] = null;
}
if (this.minAspectRatio != null) {
json[r'minAspectRatio'] = this.minAspectRatio;
} else {
// json[r'minAspectRatio'] = null;
}
if (this.minHeight != null) {
json[r'minHeight'] = this.minHeight;
} else {
// json[r'minHeight'] = null;
}
if (this.minWidth != null) {
json[r'minWidth'] = this.minWidth;
} else {
// json[r'minWidth'] = null;
}
if (this.model != null) {
json[r'model'] = this.model;
} else {
@ -544,6 +662,12 @@ class RandomSearchDto {
lensModel: mapValueOfType<String>(json, r'lensModel'),
libraryId: mapValueOfType<String>(json, r'libraryId'),
make: mapValueOfType<String>(json, r'make'),
maxAspectRatio: num.parse('${json[r'maxAspectRatio']}'),
maxHeight: mapValueOfType<int>(json, r'maxHeight'),
maxWidth: mapValueOfType<int>(json, r'maxWidth'),
minAspectRatio: num.parse('${json[r'minAspectRatio']}'),
minHeight: mapValueOfType<int>(json, r'minHeight'),
minWidth: mapValueOfType<int>(json, r'minWidth'),
model: mapValueOfType<String>(json, r'model'),
ocr: mapValueOfType<String>(json, r'ocr'),
personIds: json[r'personIds'] is Iterable

View File

@ -27,6 +27,12 @@ class SmartSearchDto {
this.lensModel,
this.libraryId,
this.make,
this.maxAspectRatio,
this.maxHeight,
this.maxWidth,
this.minAspectRatio,
this.minHeight,
this.minWidth,
this.model,
this.ocr,
this.page,
@ -139,6 +145,76 @@ class SmartSearchDto {
/// Filter by camera make
String? make;
/// Filter by maximum aspect ratio (width/height)
///
/// Minimum value: 0
///
/// 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? maxAspectRatio;
/// Filter by maximum image height
///
/// Minimum value: 1
/// Maximum value: 9007199254740991
///
/// 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.
///
int? maxHeight;
/// Filter by maximum image width
///
/// Minimum value: 1
/// Maximum value: 9007199254740991
///
/// 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.
///
int? maxWidth;
/// Filter by minimum aspect ratio (width/height)
///
/// Minimum value: 0
///
/// 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? minAspectRatio;
/// Filter by minimum image height
///
/// Minimum value: 1
/// Maximum value: 9007199254740991
///
/// 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.
///
int? minHeight;
/// Filter by minimum image width
///
/// Minimum value: 1
/// Maximum value: 9007199254740991
///
/// 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.
///
int? minWidth;
/// Filter by camera model
String? model;
@ -312,6 +388,12 @@ class SmartSearchDto {
other.lensModel == lensModel &&
other.libraryId == libraryId &&
other.make == make &&
other.maxAspectRatio == maxAspectRatio &&
other.maxHeight == maxHeight &&
other.maxWidth == maxWidth &&
other.minAspectRatio == minAspectRatio &&
other.minHeight == minHeight &&
other.minWidth == minWidth &&
other.model == model &&
other.ocr == ocr &&
other.page == page &&
@ -350,6 +432,12 @@ class SmartSearchDto {
(lensModel == null ? 0 : lensModel!.hashCode) +
(libraryId == null ? 0 : libraryId!.hashCode) +
(make == null ? 0 : make!.hashCode) +
(maxAspectRatio == null ? 0 : maxAspectRatio!.hashCode) +
(maxHeight == null ? 0 : maxHeight!.hashCode) +
(maxWidth == null ? 0 : maxWidth!.hashCode) +
(minAspectRatio == null ? 0 : minAspectRatio!.hashCode) +
(minHeight == null ? 0 : minHeight!.hashCode) +
(minWidth == null ? 0 : minWidth!.hashCode) +
(model == null ? 0 : model!.hashCode) +
(ocr == null ? 0 : ocr!.hashCode) +
(page == null ? 0 : page!.hashCode) +
@ -372,7 +460,7 @@ class SmartSearchDto {
(withExif == null ? 0 : withExif!.hashCode);
@override
String toString() => 'SmartSearchDto[albumIds=$albumIds, city=$city, country=$country, createdAfter=$createdAfter, createdBefore=$createdBefore, 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]';
String toString() => 'SmartSearchDto[albumIds=$albumIds, city=$city, country=$country, createdAfter=$createdAfter, createdBefore=$createdBefore, isEncoded=$isEncoded, isFavorite=$isFavorite, isMotion=$isMotion, isNotInAlbum=$isNotInAlbum, isOffline=$isOffline, language=$language, lensModel=$lensModel, libraryId=$libraryId, make=$make, maxAspectRatio=$maxAspectRatio, maxHeight=$maxHeight, maxWidth=$maxWidth, minAspectRatio=$minAspectRatio, minHeight=$minHeight, minWidth=$minWidth, 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<String, dynamic> toJson() {
final json = <String, dynamic>{};
@ -446,6 +534,36 @@ class SmartSearchDto {
} else {
// json[r'make'] = null;
}
if (this.maxAspectRatio != null) {
json[r'maxAspectRatio'] = this.maxAspectRatio;
} else {
// json[r'maxAspectRatio'] = null;
}
if (this.maxHeight != null) {
json[r'maxHeight'] = this.maxHeight;
} else {
// json[r'maxHeight'] = null;
}
if (this.maxWidth != null) {
json[r'maxWidth'] = this.maxWidth;
} else {
// json[r'maxWidth'] = null;
}
if (this.minAspectRatio != null) {
json[r'minAspectRatio'] = this.minAspectRatio;
} else {
// json[r'minAspectRatio'] = null;
}
if (this.minHeight != null) {
json[r'minHeight'] = this.minHeight;
} else {
// json[r'minHeight'] = null;
}
if (this.minWidth != null) {
json[r'minWidth'] = this.minWidth;
} else {
// json[r'minWidth'] = null;
}
if (this.model != null) {
json[r'model'] = this.model;
} else {
@ -582,6 +700,12 @@ class SmartSearchDto {
lensModel: mapValueOfType<String>(json, r'lensModel'),
libraryId: mapValueOfType<String>(json, r'libraryId'),
make: mapValueOfType<String>(json, r'make'),
maxAspectRatio: num.parse('${json[r'maxAspectRatio']}'),
maxHeight: mapValueOfType<int>(json, r'maxHeight'),
maxWidth: mapValueOfType<int>(json, r'maxWidth'),
minAspectRatio: num.parse('${json[r'minAspectRatio']}'),
minHeight: mapValueOfType<int>(json, r'minHeight'),
minWidth: mapValueOfType<int>(json, r'minWidth'),
model: mapValueOfType<String>(json, r'model'),
ocr: mapValueOfType<String>(json, r'ocr'),
page: mapValueOfType<int>(json, r'page'),

View File

@ -27,6 +27,12 @@ class StatisticsSearchDto {
this.lensModel,
this.libraryId,
this.make,
this.maxAspectRatio,
this.maxHeight,
this.maxWidth,
this.minAspectRatio,
this.minHeight,
this.minWidth,
this.model,
this.ocr,
this.personIds = const [],
@ -133,6 +139,76 @@ class StatisticsSearchDto {
/// Filter by camera make
String? make;
/// Filter by maximum aspect ratio (width/height)
///
/// Minimum value: 0
///
/// 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? maxAspectRatio;
/// Filter by maximum image height
///
/// Minimum value: 1
/// Maximum value: 9007199254740991
///
/// 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.
///
int? maxHeight;
/// Filter by maximum image width
///
/// Minimum value: 1
/// Maximum value: 9007199254740991
///
/// 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.
///
int? maxWidth;
/// Filter by minimum aspect ratio (width/height)
///
/// Minimum value: 0
///
/// 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? minAspectRatio;
/// Filter by minimum image height
///
/// Minimum value: 1
/// Maximum value: 9007199254740991
///
/// 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.
///
int? minHeight;
/// Filter by minimum image width
///
/// Minimum value: 1
/// Maximum value: 9007199254740991
///
/// 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.
///
int? minWidth;
/// Filter by camera model
String? model;
@ -246,6 +322,12 @@ class StatisticsSearchDto {
other.lensModel == lensModel &&
other.libraryId == libraryId &&
other.make == make &&
other.maxAspectRatio == maxAspectRatio &&
other.maxHeight == maxHeight &&
other.maxWidth == maxWidth &&
other.minAspectRatio == minAspectRatio &&
other.minHeight == minHeight &&
other.minWidth == minWidth &&
other.model == model &&
other.ocr == ocr &&
_deepEquality.equals(other.personIds, personIds) &&
@ -278,6 +360,12 @@ class StatisticsSearchDto {
(lensModel == null ? 0 : lensModel!.hashCode) +
(libraryId == null ? 0 : libraryId!.hashCode) +
(make == null ? 0 : make!.hashCode) +
(maxAspectRatio == null ? 0 : maxAspectRatio!.hashCode) +
(maxHeight == null ? 0 : maxHeight!.hashCode) +
(maxWidth == null ? 0 : maxWidth!.hashCode) +
(minAspectRatio == null ? 0 : minAspectRatio!.hashCode) +
(minHeight == null ? 0 : minHeight!.hashCode) +
(minWidth == null ? 0 : minWidth!.hashCode) +
(model == null ? 0 : model!.hashCode) +
(ocr == null ? 0 : ocr!.hashCode) +
(personIds.hashCode) +
@ -294,7 +382,7 @@ class StatisticsSearchDto {
(visibility == null ? 0 : visibility!.hashCode);
@override
String toString() => 'StatisticsSearchDto[albumIds=$albumIds, city=$city, country=$country, createdAfter=$createdAfter, createdBefore=$createdBefore, description=$description, 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]';
String toString() => 'StatisticsSearchDto[albumIds=$albumIds, city=$city, country=$country, createdAfter=$createdAfter, createdBefore=$createdBefore, description=$description, isEncoded=$isEncoded, isFavorite=$isFavorite, isMotion=$isMotion, isNotInAlbum=$isNotInAlbum, isOffline=$isOffline, lensModel=$lensModel, libraryId=$libraryId, make=$make, maxAspectRatio=$maxAspectRatio, maxHeight=$maxHeight, maxWidth=$maxWidth, minAspectRatio=$minAspectRatio, minHeight=$minHeight, minWidth=$minWidth, 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<String, dynamic> toJson() {
final json = <String, dynamic>{};
@ -368,6 +456,36 @@ class StatisticsSearchDto {
} else {
// json[r'make'] = null;
}
if (this.maxAspectRatio != null) {
json[r'maxAspectRatio'] = this.maxAspectRatio;
} else {
// json[r'maxAspectRatio'] = null;
}
if (this.maxHeight != null) {
json[r'maxHeight'] = this.maxHeight;
} else {
// json[r'maxHeight'] = null;
}
if (this.maxWidth != null) {
json[r'maxWidth'] = this.maxWidth;
} else {
// json[r'maxWidth'] = null;
}
if (this.minAspectRatio != null) {
json[r'minAspectRatio'] = this.minAspectRatio;
} else {
// json[r'minAspectRatio'] = null;
}
if (this.minHeight != null) {
json[r'minHeight'] = this.minHeight;
} else {
// json[r'minHeight'] = null;
}
if (this.minWidth != null) {
json[r'minWidth'] = this.minWidth;
} else {
// json[r'minWidth'] = null;
}
if (this.model != null) {
json[r'model'] = this.model;
} else {
@ -474,6 +592,12 @@ class StatisticsSearchDto {
lensModel: mapValueOfType<String>(json, r'lensModel'),
libraryId: mapValueOfType<String>(json, r'libraryId'),
make: mapValueOfType<String>(json, r'make'),
maxAspectRatio: num.parse('${json[r'maxAspectRatio']}'),
maxHeight: mapValueOfType<int>(json, r'maxHeight'),
maxWidth: mapValueOfType<int>(json, r'maxWidth'),
minAspectRatio: num.parse('${json[r'minAspectRatio']}'),
minHeight: mapValueOfType<int>(json, r'minHeight'),
minWidth: mapValueOfType<int>(json, r'minWidth'),
model: mapValueOfType<String>(json, r'model'),
ocr: mapValueOfType<String>(json, r'ocr'),
personIds: json[r'personIds'] is Iterable

View File

@ -9478,6 +9478,50 @@
"nullable": true
}
},
{
"name": "maxAspectRatio",
"required": false,
"in": "query",
"description": "Filter by maximum aspect ratio (width/height)",
"schema": {
"exclusiveMinimum": true,
"type": "number",
"minimum": 0
}
},
{
"name": "maxHeight",
"required": false,
"in": "query",
"description": "Filter by maximum image height",
"schema": {
"minimum": 1,
"maximum": 9007199254740991,
"type": "integer"
}
},
{
"name": "maxWidth",
"required": false,
"in": "query",
"description": "Filter by maximum image width",
"schema": {
"minimum": 1,
"maximum": 9007199254740991,
"type": "integer"
}
},
{
"name": "minAspectRatio",
"required": false,
"in": "query",
"description": "Filter by minimum aspect ratio (width/height)",
"schema": {
"exclusiveMinimum": true,
"type": "number",
"minimum": 0
}
},
{
"name": "minFileSize",
"required": false,
@ -9489,6 +9533,28 @@
"type": "integer"
}
},
{
"name": "minHeight",
"required": false,
"in": "query",
"description": "Filter by minimum image height",
"schema": {
"minimum": 1,
"maximum": 9007199254740991,
"type": "integer"
}
},
{
"name": "minWidth",
"required": false,
"in": "query",
"description": "Filter by minimum image width",
"schema": {
"minimum": 1,
"maximum": 9007199254740991,
"type": "integer"
}
},
{
"name": "model",
"required": false,
@ -18978,6 +19044,42 @@
"nullable": true,
"type": "string"
},
"maxAspectRatio": {
"description": "Filter by maximum aspect ratio (width/height)",
"exclusiveMinimum": true,
"minimum": 0,
"type": "number"
},
"maxHeight": {
"description": "Filter by maximum image height",
"maximum": 9007199254740991,
"minimum": 1,
"type": "integer"
},
"maxWidth": {
"description": "Filter by maximum image width",
"maximum": 9007199254740991,
"minimum": 1,
"type": "integer"
},
"minAspectRatio": {
"description": "Filter by minimum aspect ratio (width/height)",
"exclusiveMinimum": true,
"minimum": 0,
"type": "number"
},
"minHeight": {
"description": "Filter by minimum image height",
"maximum": 9007199254740991,
"minimum": 1,
"type": "integer"
},
"minWidth": {
"description": "Filter by minimum image width",
"maximum": 9007199254740991,
"minimum": 1,
"type": "integer"
},
"model": {
"description": "Filter by camera model",
"nullable": true,
@ -20662,6 +20764,42 @@
"nullable": true,
"type": "string"
},
"maxAspectRatio": {
"description": "Filter by maximum aspect ratio (width/height)",
"exclusiveMinimum": true,
"minimum": 0,
"type": "number"
},
"maxHeight": {
"description": "Filter by maximum image height",
"maximum": 9007199254740991,
"minimum": 1,
"type": "integer"
},
"maxWidth": {
"description": "Filter by maximum image width",
"maximum": 9007199254740991,
"minimum": 1,
"type": "integer"
},
"minAspectRatio": {
"description": "Filter by minimum aspect ratio (width/height)",
"exclusiveMinimum": true,
"minimum": 0,
"type": "number"
},
"minHeight": {
"description": "Filter by minimum image height",
"maximum": 9007199254740991,
"minimum": 1,
"type": "integer"
},
"minWidth": {
"description": "Filter by minimum image width",
"maximum": 9007199254740991,
"minimum": 1,
"type": "integer"
},
"model": {
"description": "Filter by camera model",
"nullable": true,
@ -22037,6 +22175,42 @@
"nullable": true,
"type": "string"
},
"maxAspectRatio": {
"description": "Filter by maximum aspect ratio (width/height)",
"exclusiveMinimum": true,
"minimum": 0,
"type": "number"
},
"maxHeight": {
"description": "Filter by maximum image height",
"maximum": 9007199254740991,
"minimum": 1,
"type": "integer"
},
"maxWidth": {
"description": "Filter by maximum image width",
"maximum": 9007199254740991,
"minimum": 1,
"type": "integer"
},
"minAspectRatio": {
"description": "Filter by minimum aspect ratio (width/height)",
"exclusiveMinimum": true,
"minimum": 0,
"type": "number"
},
"minHeight": {
"description": "Filter by minimum image height",
"maximum": 9007199254740991,
"minimum": 1,
"type": "integer"
},
"minWidth": {
"description": "Filter by minimum image width",
"maximum": 9007199254740991,
"minimum": 1,
"type": "integer"
},
"model": {
"description": "Filter by camera model",
"nullable": true,
@ -22313,6 +22487,42 @@
"nullable": true,
"type": "string"
},
"maxAspectRatio": {
"description": "Filter by maximum aspect ratio (width/height)",
"exclusiveMinimum": true,
"minimum": 0,
"type": "number"
},
"maxHeight": {
"description": "Filter by maximum image height",
"maximum": 9007199254740991,
"minimum": 1,
"type": "integer"
},
"maxWidth": {
"description": "Filter by maximum image width",
"maximum": 9007199254740991,
"minimum": 1,
"type": "integer"
},
"minAspectRatio": {
"description": "Filter by minimum aspect ratio (width/height)",
"exclusiveMinimum": true,
"minimum": 0,
"type": "number"
},
"minHeight": {
"description": "Filter by minimum image height",
"maximum": 9007199254740991,
"minimum": 1,
"type": "integer"
},
"minWidth": {
"description": "Filter by minimum image width",
"maximum": 9007199254740991,
"minimum": 1,
"type": "integer"
},
"model": {
"description": "Filter by camera model",
"nullable": true,

View File

@ -1608,6 +1608,18 @@ export type MetadataSearchDto = {
libraryId?: string | null;
/** Filter by camera make */
make?: string | null;
/** Filter by maximum aspect ratio (width/height) */
maxAspectRatio?: number;
/** Filter by maximum image height */
maxHeight?: number;
/** Filter by maximum image width */
maxWidth?: number;
/** Filter by minimum aspect ratio (width/height) */
minAspectRatio?: number;
/** Filter by minimum image height */
minHeight?: number;
/** Filter by minimum image width */
minWidth?: number;
/** Filter by camera model */
model?: string | null;
/** Filter by OCR text content */
@ -1729,6 +1741,18 @@ export type RandomSearchDto = {
libraryId?: string | null;
/** Filter by camera make */
make?: string | null;
/** Filter by maximum aspect ratio (width/height) */
maxAspectRatio?: number;
/** Filter by maximum image height */
maxHeight?: number;
/** Filter by maximum image width */
maxWidth?: number;
/** Filter by minimum aspect ratio (width/height) */
minAspectRatio?: number;
/** Filter by minimum image height */
minHeight?: number;
/** Filter by minimum image width */
minWidth?: number;
/** Filter by camera model */
model?: string | null;
/** Filter by OCR text content */
@ -1795,6 +1819,18 @@ export type SmartSearchDto = {
libraryId?: string | null;
/** Filter by camera make */
make?: string | null;
/** Filter by maximum aspect ratio (width/height) */
maxAspectRatio?: number;
/** Filter by maximum image height */
maxHeight?: number;
/** Filter by maximum image width */
maxWidth?: number;
/** Filter by minimum aspect ratio (width/height) */
minAspectRatio?: number;
/** Filter by minimum image height */
minHeight?: number;
/** Filter by minimum image width */
minWidth?: number;
/** Filter by camera model */
model?: string | null;
/** Filter by OCR text content */
@ -1863,6 +1899,18 @@ export type StatisticsSearchDto = {
libraryId?: string | null;
/** Filter by camera make */
make?: string | null;
/** Filter by maximum aspect ratio (width/height) */
maxAspectRatio?: number;
/** Filter by maximum image height */
maxHeight?: number;
/** Filter by maximum image width */
maxWidth?: number;
/** Filter by minimum aspect ratio (width/height) */
minAspectRatio?: number;
/** Filter by minimum image height */
minHeight?: number;
/** Filter by minimum image width */
minWidth?: number;
/** Filter by camera model */
model?: string | null;
/** Filter by OCR text content */
@ -5388,7 +5436,7 @@ export function getExploreData(opts?: Oazapfts.RequestOpts) {
/**
* Search large assets
*/
export function searchLargeAssets({ albumIds, city, country, createdAfter, createdBefore, 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 }: {
export function searchLargeAssets({ albumIds, city, country, createdAfter, createdBefore, isEncoded, isFavorite, isMotion, isNotInAlbum, isOffline, lensModel, libraryId, make, maxAspectRatio, maxHeight, maxWidth, minAspectRatio, minFileSize, minHeight, minWidth, 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;
@ -5402,7 +5450,13 @@ export function searchLargeAssets({ albumIds, city, country, createdAfter, creat
lensModel?: string | null;
libraryId?: string | null;
make?: string | null;
maxAspectRatio?: number;
maxHeight?: number;
maxWidth?: number;
minAspectRatio?: number;
minFileSize?: number;
minHeight?: number;
minWidth?: number;
model?: string | null;
ocr?: string;
personIds?: string[];
@ -5438,7 +5492,13 @@ export function searchLargeAssets({ albumIds, city, country, createdAfter, creat
lensModel,
libraryId,
make,
maxAspectRatio,
maxHeight,
maxWidth,
minAspectRatio,
minFileSize,
minHeight,
minWidth,
model,
ocr,
personIds,

View File

@ -120,6 +120,42 @@ describe(SearchController.name, () => {
);
});
it('should reject minWidth as a negative number', async () => {
const { status, body } = await request(ctx.getHttpServer()).post('/search/metadata').send({ minWidth: -1 });
expect(status).toBe(400);
expect(body).toEqual(
errorDto.validationError([{ path: ['minWidth'], message: 'Too small: expected number to be >=1' }]),
);
});
it('should reject minAspectRatio as zero', async () => {
const { status, body } = await request(ctx.getHttpServer()).post('/search/metadata').send({ minAspectRatio: 0 });
expect(status).toBe(400);
expect(body).toEqual(
errorDto.validationError([{ path: ['minAspectRatio'], message: expect.stringContaining('Too small') }]),
);
});
it('should reject maxAspectRatio as zero', async () => {
const { status, body } = await request(ctx.getHttpServer()).post('/search/metadata').send({ maxAspectRatio: 0 });
expect(status).toBe(400);
expect(body).toEqual(
errorDto.validationError([{ path: ['maxAspectRatio'], message: expect.stringContaining('Too small') }]),
);
});
it('should reject minAspectRatio as a string', async () => {
const { status, body } = await request(ctx.getHttpServer())
.post('/search/metadata')
.send({ minAspectRatio: 'abc' });
expect(status).toBe(400);
expect(body).toEqual(
errorDto.validationError([
{ path: ['minAspectRatio'], message: 'Invalid input: expected number, received NaN' },
]),
);
});
describe('POST /search/random', () => {
it('should be an authenticated route', async () => {
await request(ctx.getHttpServer()).post('/search/random');

View File

@ -29,6 +29,12 @@ const BaseSearchSchema = z.object({
make: emptyStringToNull(z.string().nullable()).optional().describe('Filter by camera make'),
model: emptyStringToNull(z.string().nullable()).optional().describe('Filter by camera model'),
lensModel: emptyStringToNull(z.string().nullable()).optional().describe('Filter by lens model'),
minAspectRatio: z.coerce.number().positive().optional().describe('Filter by minimum aspect ratio (width/height)'),
maxAspectRatio: z.coerce.number().positive().optional().describe('Filter by maximum aspect ratio (width/height)'),
minWidth: z.coerce.number().int().min(1).optional().describe('Filter by minimum image width'),
maxWidth: z.coerce.number().int().min(1).optional().describe('Filter by maximum image width'),
minHeight: z.coerce.number().int().min(1).optional().describe('Filter by minimum image height'),
maxHeight: z.coerce.number().int().min(1).optional().describe('Filter by maximum image height'),
isNotInAlbum: z.boolean().optional().describe('Filter assets not in any album'),
personIds: z.array(z.uuidv4()).optional().describe('Filter by person IDs'),
tagIds: z.array(z.uuidv4()).nullish().describe('Filter by tag IDs'),

View File

@ -69,6 +69,12 @@ export interface SearchExifOptions {
country?: string | null;
lensModel?: string | null;
make?: string | null;
maxAspectRatio?: number;
maxHeight?: number;
maxWidth?: number;
minAspectRatio?: number;
minHeight?: number;
minWidth?: number;
model?: string | null;
state?: string | null;
description?: string | null;

View File

@ -425,6 +425,16 @@ export function searchAssetBuilder(kysely: Kysely<DB>, options: AssetSearchBuild
.innerJoin('asset_exif', 'asset.id', 'asset_exif.assetId')
.where('asset_exif.rating', options.rating === null ? 'is' : '=', options.rating!),
)
.$if(options.minAspectRatio !== undefined, (qb) =>
qb.where(sql`asset.width::double precision / nullif(asset.height, 0)`, '>=', options.minAspectRatio!),
)
.$if(options.maxAspectRatio !== undefined, (qb) =>
qb.where(sql`asset.width::double precision / nullif(asset.height, 0)`, '<=', options.maxAspectRatio!),
)
.$if(options.minWidth !== undefined, (qb) => qb.where('asset.width', '>=', options.minWidth!))
.$if(options.maxWidth !== undefined, (qb) => qb.where('asset.width', '<=', options.maxWidth!))
.$if(options.minHeight !== undefined, (qb) => qb.where('asset.height', '>=', options.minHeight!))
.$if(options.maxHeight !== undefined, (qb) => qb.where('asset.height', '<=', options.maxHeight!))
.$if(!!options.checksum, (qb) => qb.where('asset.checksum', '=', options.checksum!))
.$if(!!options.id, (qb) => qb.where('asset.id', '=', asUuid(options.id!)))
.$if(!!options.libraryId, (qb) => qb.where('asset.libraryId', '=', asUuid(options.libraryId!)))

View File

@ -110,6 +110,83 @@ describe(SearchService.name, () => {
});
});
describe('aspect ratio filter', () => {
it('should filter by minAspectRatio', async () => {
const { sut, ctx } = setup();
const { user } = await ctx.newUser();
const { asset: wideAsset } = await ctx.newAsset({ ownerId: user.id, width: 4000, height: 1000 });
await ctx.newAsset({ ownerId: user.id, width: 3000, height: 3000 });
await ctx.newAsset({ ownerId: user.id, width: 1000, height: 4000 });
const auth = factory.auth({ user: { id: user.id } });
const response = await sut.searchMetadata(auth, { minAspectRatio: 2 });
expect(response.assets.items).toEqual([expect.objectContaining({ id: wideAsset.id })]);
});
it('should filter by maxAspectRatio', async () => {
const { sut, ctx } = setup();
const { user } = await ctx.newUser();
await ctx.newAsset({ ownerId: user.id, width: 4000, height: 3000 });
const { asset: narrowAsset } = await ctx.newAsset({ ownerId: user.id, width: 1000, height: 4000 });
await ctx.newAsset({ ownerId: user.id, width: 3000, height: 3000 });
const auth = factory.auth({ user: { id: user.id } });
const response = await sut.searchMetadata(auth, { maxAspectRatio: 0.5 });
expect(response.assets.items).toEqual([expect.objectContaining({ id: narrowAsset.id })]);
});
it('should filter by both minAspectRatio and maxAspectRatio', async () => {
const { sut, ctx } = setup();
const { user } = await ctx.newUser();
// ratio 4:3 = 1.333 — within [1.2, 1.5]
const { asset: inRangeAsset } = await ctx.newAsset({ ownerId: user.id, width: 4000, height: 3000 });
// ratio 4:1 = 4.0 — above max
await ctx.newAsset({ ownerId: user.id, width: 4000, height: 1000 });
// ratio 1:4 = 0.25 — below min
await ctx.newAsset({ ownerId: user.id, width: 1000, height: 4000 });
const auth = factory.auth({ user: { id: user.id } });
const response = await sut.searchMetadata(auth, { minAspectRatio: 1.2, maxAspectRatio: 1.5 });
expect(response.assets.items).toEqual([expect.objectContaining({ id: inRangeAsset.id })]);
});
});
describe('resolution filter', () => {
it('should filter by minimum width and height', async () => {
const { sut, ctx } = setup();
const { user } = await ctx.newUser();
const { asset: highResAsset } = await ctx.newAsset({ ownerId: user.id, width: 4000, height: 3000 });
await ctx.newAsset({ ownerId: user.id, width: 3000, height: 3000 });
await ctx.newAsset({ ownerId: user.id, width: 4000, height: 2000 });
const auth = factory.auth({ user: { id: user.id } });
const response = await sut.searchMetadata(auth, { minWidth: 3500, minHeight: 2500 });
expect(response.assets.items).toEqual([expect.objectContaining({ id: highResAsset.id })]);
});
it('should filter by maximum width and height', async () => {
const { sut, ctx } = setup();
const { user } = await ctx.newUser();
await ctx.newAsset({ ownerId: user.id, width: 2000, height: 1500 });
const { asset: lowResAsset } = await ctx.newAsset({ ownerId: user.id, width: 1000, height: 800 });
await ctx.newAsset({ ownerId: user.id, width: 1200, height: 1600 });
const auth = factory.auth({ user: { id: user.id } });
const response = await sut.searchMetadata(auth, { maxWidth: 1200, maxHeight: 1000 });
expect(response.assets.items).toEqual([expect.objectContaining({ id: lowResAsset.id })]);
});
});
describe('getSearchSuggestions', () => {
it('should filter out empty search suggestions', async () => {
const { sut, ctx } = setup();

View File

@ -0,0 +1,79 @@
<script lang="ts">
import type { SearchImagePropsFilter } from '$lib/types';
import { Input, Text } from '@immich/ui';
import { t } from 'svelte-i18n';
type Props = {
filters: SearchImagePropsFilter;
};
let { filters = $bindable() }: Props = $props();
</script>
<div id="image-selection">
<Text fontWeight="medium">{$t('image_properties')}</Text>
<div class="mt-1 grid grid-auto-fit-40 gap-5">
<fieldset>
<Text class="mb-1 block text-xs font-light text-neutral-500" fontWeight="medium">{$t('aspect_ratio')}</Text>
<div class="grid grid-cols-2 gap-1">
<Input
type="text"
placeholder={$t('min_width')}
aria-label={`${$t('min_aspect_ratio')} ${$t('width')}`}
bind:value={filters.minAspectRatioWidth}
/>
<div class="flex items-center gap-1">
<Text aria-hidden="true" class="text-sm text-neutral-500">:</Text>
<Input
type="text"
placeholder={$t('min_height')}
aria-label={`${$t('min_aspect_ratio')} ${$t('height')}`}
bind:value={filters.minAspectRatioHeight}
/>
</div>
<Input
type="text"
placeholder={$t('max_width')}
aria-label={`${$t('max_aspect_ratio')} ${$t('width')}`}
bind:value={filters.maxAspectRatioWidth}
/>
<div class="flex items-center gap-1">
<Text aria-hidden="true" class="text-sm text-neutral-500">:</Text>
<Input
type="text"
placeholder={$t('max_height')}
aria-label={`${$t('max_aspect_ratio')} ${$t('height')}`}
bind:value={filters.maxAspectRatioHeight}
/>
</div>
</div>
</fieldset>
<fieldset>
<Text class="mb-1 block text-xs font-light text-neutral-500" fontWeight="medium">{$t('resolution')}</Text>
<div class="grid grid-cols-2 gap-1">
<Input type="text" placeholder={$t('min_width')} aria-label={$t('min_width')} bind:value={filters.minWidth} />
<div class="flex items-center gap-1">
<Text aria-hidden="true" class="text-sm text-neutral-500">x</Text>
<Input
type="text"
placeholder={$t('min_height')}
aria-label={$t('min_height')}
bind:value={filters.minHeight}
/>
</div>
<Input type="text" placeholder={$t('max_width')} aria-label={$t('max_width')} bind:value={filters.maxWidth} />
<div class="flex items-center gap-1">
<Text aria-hidden="true" class="text-sm text-neutral-500">x</Text>
<Input
type="text"
placeholder={$t('max_height')}
aria-label={$t('max_height')}
bind:value={filters.maxHeight}
/>
</div>
</div>
</fieldset>
</div>
</div>

View File

@ -8,6 +8,7 @@
import SearchRatingsSection from '$lib/components/shared-components/search-bar/SearchRatingsSection.svelte';
import SearchTagsSection from '$lib/components/shared-components/search-bar/SearchTagsSection.svelte';
import SearchTextSection from '$lib/components/shared-components/search-bar/SearchTextSection.svelte';
import SearchImagePropsSection from '$lib/components/shared-components/search-bar/SearchImagePropsSection.svelte';
import { MediaType, QueryType, validQueryTypes } from '$lib/constants';
import { authManager } from '$lib/managers/auth-manager.svelte';
import type { SearchFilter } from '$lib/types';
@ -28,6 +29,73 @@
let { searchQuery, onClose }: Props = $props();
const parseOptionalDate = (dateString?: DateTime) => (dateString ? parseUtcDate(dateString.toString()) : undefined);
const numberToOptionalString = (value?: number | null) =>
value === null || value === undefined ? undefined : value.toString();
const parseOptionalPositiveInteger = (value?: string) => {
if (!value?.trim()) {
return undefined;
}
const trimmed = value.trim();
const parsed = Number(trimmed);
return Number.isInteger(parsed) && parsed > 0 ? parsed : undefined;
};
const greatestCommonDivisor = (left: number, right: number): number => {
let a = Math.abs(left);
let b = Math.abs(right);
while (b !== 0) {
[a, b] = [b, a % b];
}
return a || 1;
};
const ratioToOptionalPair = (value?: number | null) => {
if (value === null || value === undefined || !Number.isFinite(value) || value <= 0) {
return { width: undefined, height: undefined };
}
let bestWidth = 1;
let bestHeight = 1;
let bestError = Number.POSITIVE_INFINITY;
for (let denominator = 1; denominator <= 1000; denominator++) {
const numerator = Math.max(1, Math.round(value * denominator));
const error = Math.abs(numerator / denominator - value);
if (error < bestError) {
bestWidth = numerator;
bestHeight = denominator;
bestError = error;
}
if (error < Number.EPSILON) {
break;
}
}
const divisor = greatestCommonDivisor(bestWidth, bestHeight);
return {
width: numberToOptionalString(bestWidth / divisor),
height: numberToOptionalString(bestHeight / divisor),
};
};
const parseOptionalAspectRatio = (width?: string, height?: string) => {
const parsedWidth = parseOptionalPositiveInteger(width);
const parsedHeight = parseOptionalPositiveInteger(height);
if (!parsedWidth || !parsedHeight) {
return undefined;
}
return parsedWidth / parsedHeight;
};
const toStartOfDayDate = (dateString: string) => parseUtcDate(dateString)?.startOf('day') || undefined;
const formId = generateId();
@ -58,6 +126,9 @@
query = searchQuery.originalPath;
}
const minAspectRatio = ratioToOptionalPair(searchQuery.minAspectRatio);
const maxAspectRatio = ratioToOptionalPair(searchQuery.maxAspectRatio);
return {
query,
ocr: searchQuery.ocr,
@ -96,6 +167,16 @@
? MediaType.Video
: MediaType.All,
rating: searchQuery.rating,
imageProperties: {
minAspectRatioWidth: minAspectRatio.width,
minAspectRatioHeight: minAspectRatio.height,
maxAspectRatioWidth: maxAspectRatio.width,
maxAspectRatioHeight: maxAspectRatio.height,
minWidth: numberToOptionalString(searchQuery.minWidth),
maxWidth: numberToOptionalString(searchQuery.maxWidth),
minHeight: numberToOptionalString(searchQuery.minHeight),
maxHeight: numberToOptionalString(searchQuery.maxHeight),
},
};
};
@ -118,6 +199,16 @@
},
mediaType: MediaType.All,
rating: undefined,
imageProperties: {
minAspectRatioWidth: undefined,
minAspectRatioHeight: undefined,
maxAspectRatioWidth: undefined,
maxAspectRatioHeight: undefined,
minWidth: undefined,
maxWidth: undefined,
minHeight: undefined,
maxHeight: undefined,
},
};
};
@ -149,6 +240,18 @@
visibility: filter.display.isArchive ? AssetVisibility.Archive : undefined,
isFavorite: filter.display.isFavorite || undefined,
isNotInAlbum: filter.display.isNotInAlbum || undefined,
minAspectRatio: parseOptionalAspectRatio(
filter.imageProperties.minAspectRatioWidth,
filter.imageProperties.minAspectRatioHeight,
),
maxAspectRatio: parseOptionalAspectRatio(
filter.imageProperties.maxAspectRatioWidth,
filter.imageProperties.maxAspectRatioHeight,
),
minWidth: parseOptionalPositiveInteger(filter.imageProperties.minWidth),
maxWidth: parseOptionalPositiveInteger(filter.imageProperties.maxWidth),
minHeight: parseOptionalPositiveInteger(filter.imageProperties.minHeight),
maxHeight: parseOptionalPositiveInteger(filter.imageProperties.maxHeight),
personIds: filter.personIds.size > 0 ? [...filter.personIds] : undefined,
tagIds: filter.tagIds === null ? null : filter.tagIds.size > 0 ? [...filter.tagIds] : undefined,
type,
@ -209,6 +312,9 @@
<!-- DISPLAY OPTIONS -->
<SearchDisplaySection bind:filters={filter.display} />
</div>
<!-- IMAGE PROPERTIES -->
<SearchImagePropsSection bind:filters={filter.imageProperties} />
</div>
</form>
</ModalBody>

View File

@ -71,6 +71,17 @@ export type SearchDisplayFilters = {
isFavorite: boolean;
};
export type SearchImagePropsFilter = {
minAspectRatioWidth?: string;
minAspectRatioHeight?: string;
maxAspectRatioWidth?: string;
maxAspectRatioHeight?: string;
minWidth?: string;
maxWidth?: string;
minHeight?: string;
maxHeight?: string;
};
export type SearchLocationFilter = {
country?: string;
state?: string;
@ -90,6 +101,7 @@ export type SearchFilter = {
display: SearchDisplayFilters;
mediaType: MediaType;
rating?: number | null;
imageProperties: SearchImagePropsFilter;
};
export type JSONSchemaType = 'string' | 'number' | 'integer' | 'boolean' | 'object';

View File

@ -189,6 +189,12 @@
description: $t('description'),
queryAssetId: $t('query_asset_id'),
ocr: $t('ocr'),
minAspectRatio: $t('min_aspect_ratio'),
maxAspectRatio: $t('max_aspect_ratio'),
minWidth: $t('min_width'),
maxWidth: $t('max_width'),
minHeight: $t('min_height'),
maxHeight: $t('max_height'),
};
return keyMap[key] || key;
}