mirror of
				https://github.com/immich-app/immich.git
				synced 2025-10-31 10:37:11 -04:00 
			
		
		
		
	fix: ensure manually tagged faces have proper source type (#16364)
immich-app/immich#16062 added manual face tagging and deletion, but did not add a new 'SourceType'. The create faces would default to 'machine-learning' which is incorrect, and has the annoying downside that they will be wiped when the 'Refresh Faces' job is run. Handling of non-machine-learning faces was previously added in immich-app/immich#6455. This PR simply extends it to the new manually tagged faces.
This commit is contained in:
		
							parent
							
								
									8fbd650483
								
							
						
					
					
						commit
						4b55888d16
					
				
							
								
								
									
										10
									
								
								mobile/openapi/lib/model/asset_face_create_dto.dart
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										10
									
								
								mobile/openapi/lib/model/asset_face_create_dto.dart
									
									
									
										generated
									
									
									
								
							| @ -18,6 +18,7 @@ class AssetFaceCreateDto { | ||||
|     required this.imageHeight, | ||||
|     required this.imageWidth, | ||||
|     required this.personId, | ||||
|     this.sourceType = SourceType.manual, | ||||
|     required this.width, | ||||
|     required this.x, | ||||
|     required this.y, | ||||
| @ -33,6 +34,8 @@ class AssetFaceCreateDto { | ||||
| 
 | ||||
|   String personId; | ||||
| 
 | ||||
|   SourceType sourceType; | ||||
| 
 | ||||
|   int width; | ||||
| 
 | ||||
|   int x; | ||||
| @ -46,6 +49,7 @@ class AssetFaceCreateDto { | ||||
|     other.imageHeight == imageHeight && | ||||
|     other.imageWidth == imageWidth && | ||||
|     other.personId == personId && | ||||
|     other.sourceType == sourceType && | ||||
|     other.width == width && | ||||
|     other.x == x && | ||||
|     other.y == y; | ||||
| @ -58,12 +62,13 @@ class AssetFaceCreateDto { | ||||
|     (imageHeight.hashCode) + | ||||
|     (imageWidth.hashCode) + | ||||
|     (personId.hashCode) + | ||||
|     (sourceType.hashCode) + | ||||
|     (width.hashCode) + | ||||
|     (x.hashCode) + | ||||
|     (y.hashCode); | ||||
| 
 | ||||
|   @override | ||||
|   String toString() => 'AssetFaceCreateDto[assetId=$assetId, height=$height, imageHeight=$imageHeight, imageWidth=$imageWidth, personId=$personId, width=$width, x=$x, y=$y]'; | ||||
|   String toString() => 'AssetFaceCreateDto[assetId=$assetId, height=$height, imageHeight=$imageHeight, imageWidth=$imageWidth, personId=$personId, sourceType=$sourceType, width=$width, x=$x, y=$y]'; | ||||
| 
 | ||||
|   Map<String, dynamic> toJson() { | ||||
|     final json = <String, dynamic>{}; | ||||
| @ -72,6 +77,7 @@ class AssetFaceCreateDto { | ||||
|       json[r'imageHeight'] = this.imageHeight; | ||||
|       json[r'imageWidth'] = this.imageWidth; | ||||
|       json[r'personId'] = this.personId; | ||||
|       json[r'sourceType'] = this.sourceType; | ||||
|       json[r'width'] = this.width; | ||||
|       json[r'x'] = this.x; | ||||
|       json[r'y'] = this.y; | ||||
| @ -92,6 +98,7 @@ class AssetFaceCreateDto { | ||||
|         imageHeight: mapValueOfType<int>(json, r'imageHeight')!, | ||||
|         imageWidth: mapValueOfType<int>(json, r'imageWidth')!, | ||||
|         personId: mapValueOfType<String>(json, r'personId')!, | ||||
|         sourceType: SourceType.fromJson(json[r'sourceType'])!, | ||||
|         width: mapValueOfType<int>(json, r'width')!, | ||||
|         x: mapValueOfType<int>(json, r'x')!, | ||||
|         y: mapValueOfType<int>(json, r'y')!, | ||||
| @ -147,6 +154,7 @@ class AssetFaceCreateDto { | ||||
|     'imageHeight', | ||||
|     'imageWidth', | ||||
|     'personId', | ||||
|     'sourceType', | ||||
|     'width', | ||||
|     'x', | ||||
|     'y', | ||||
|  | ||||
							
								
								
									
										3
									
								
								mobile/openapi/lib/model/source_type.dart
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										3
									
								
								mobile/openapi/lib/model/source_type.dart
									
									
									
										generated
									
									
									
								
							| @ -25,11 +25,13 @@ class SourceType { | ||||
| 
 | ||||
|   static const machineLearning = SourceType._(r'machine-learning'); | ||||
|   static const exif = SourceType._(r'exif'); | ||||
|   static const manual = SourceType._(r'manual'); | ||||
| 
 | ||||
|   /// List of all possible values in this [enum][SourceType]. | ||||
|   static const values = <SourceType>[ | ||||
|     machineLearning, | ||||
|     exif, | ||||
|     manual, | ||||
|   ]; | ||||
| 
 | ||||
|   static SourceType? fromJson(dynamic value) => SourceTypeTypeTransformer().decode(value); | ||||
| @ -70,6 +72,7 @@ class SourceTypeTypeTransformer { | ||||
|       switch (data) { | ||||
|         case r'machine-learning': return SourceType.machineLearning; | ||||
|         case r'exif': return SourceType.exif; | ||||
|         case r'manual': return SourceType.manual; | ||||
|         default: | ||||
|           if (!allowNull) { | ||||
|             throw ArgumentError('Unknown enum value to decode: $data'); | ||||
|  | ||||
| @ -8301,6 +8301,14 @@ | ||||
|             "format": "uuid", | ||||
|             "type": "string" | ||||
|           }, | ||||
|           "sourceType": { | ||||
|             "allOf": [ | ||||
|               { | ||||
|                 "$ref": "#/components/schemas/SourceType" | ||||
|               } | ||||
|             ], | ||||
|             "default": "manual" | ||||
|           }, | ||||
|           "width": { | ||||
|             "type": "integer" | ||||
|           }, | ||||
| @ -8317,6 +8325,7 @@ | ||||
|           "imageHeight", | ||||
|           "imageWidth", | ||||
|           "personId", | ||||
|           "sourceType", | ||||
|           "width", | ||||
|           "x", | ||||
|           "y" | ||||
| @ -11952,7 +11961,8 @@ | ||||
|       "SourceType": { | ||||
|         "enum": [ | ||||
|           "machine-learning", | ||||
|           "exif" | ||||
|           "exif", | ||||
|           "manual" | ||||
|         ], | ||||
|         "type": "string" | ||||
|       }, | ||||
|  | ||||
| @ -529,6 +529,7 @@ export type AssetFaceCreateDto = { | ||||
|     imageHeight: number; | ||||
|     imageWidth: number; | ||||
|     personId: string; | ||||
|     sourceType: SourceType; | ||||
|     width: number; | ||||
|     x: number; | ||||
|     y: number; | ||||
| @ -3453,7 +3454,8 @@ export enum AlbumUserRole { | ||||
| } | ||||
| export enum SourceType { | ||||
|     MachineLearning = "machine-learning", | ||||
|     Exif = "exif" | ||||
|     Exif = "exif", | ||||
|     Manual = "manual" | ||||
| } | ||||
| export enum AssetTypeEnum { | ||||
|     Image = "IMAGE", | ||||
|  | ||||
							
								
								
									
										2
									
								
								server/src/db.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								server/src/db.d.ts
									
									
									
									
										vendored
									
									
								
							| @ -29,7 +29,7 @@ export type JsonPrimitive = boolean | number | string | null; | ||||
| 
 | ||||
| export type JsonValue = JsonArray | JsonObject | JsonPrimitive; | ||||
| 
 | ||||
| export type Sourcetype = 'exif' | 'machine-learning'; | ||||
| export type Sourcetype = 'exif' | 'machine-learning' | 'manual'; | ||||
| 
 | ||||
| export type Timestamp = ColumnType<Date, Date | string, Date | string>; | ||||
| 
 | ||||
|  | ||||
| @ -1,6 +1,6 @@ | ||||
| import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; | ||||
| import { Type } from 'class-transformer'; | ||||
| import { IsArray, IsInt, IsNotEmpty, IsNumber, IsString, Max, Min, ValidateNested } from 'class-validator'; | ||||
| import { IsArray, IsEnum, IsInt, IsNotEmpty, IsNumber, IsString, Max, Min, ValidateNested } from 'class-validator'; | ||||
| import { DateTime } from 'luxon'; | ||||
| import { PropertyLifecycle } from 'src/decorators'; | ||||
| import { AuthDto } from 'src/dtos/auth.dto'; | ||||
| @ -194,6 +194,10 @@ export class AssetFaceCreateDto extends AssetFaceUpdateItem { | ||||
|   @IsNotEmpty() | ||||
|   @IsNumber() | ||||
|   height!: number; | ||||
| 
 | ||||
|   @ApiProperty({ type: 'string', enum: SourceType, enumName: 'SourceType' }) | ||||
|   @IsEnum(SourceType) | ||||
|   sourceType: SourceType = SourceType.MANUAL; | ||||
| } | ||||
| 
 | ||||
| export class AssetFaceDeleteDto { | ||||
|  | ||||
| @ -228,6 +228,7 @@ export enum AssetStatus { | ||||
| export enum SourceType { | ||||
|   MACHINE_LEARNING = 'machine-learning', | ||||
|   EXIF = 'exif', | ||||
|   MANUAL = 'manual', | ||||
| } | ||||
| 
 | ||||
| export enum ManualJobName { | ||||
|  | ||||
							
								
								
									
										27
									
								
								server/src/migrations/1740619600996-AddManualSourceType.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								server/src/migrations/1740619600996-AddManualSourceType.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,27 @@ | ||||
| import { MigrationInterface, QueryRunner } from 'typeorm'; | ||||
| 
 | ||||
| export class AddManualSourceType1740619600996 implements MigrationInterface { | ||||
|   public async up(queryRunner: QueryRunner): Promise<void> { | ||||
|     await queryRunner.query(`ALTER TYPE sourceType ADD VALUE 'manual'`); | ||||
|   } | ||||
| 
 | ||||
|   public async down(queryRunner: QueryRunner): Promise<void> { | ||||
|     // Prior to this migration, manually tagged pictures had the 'machine-learning' type
 | ||||
|     await queryRunner.query( | ||||
|       `UPDATE "asset_faces" SET "sourceType" = 'machine-learning' WHERE "sourceType" = 'manual';`, | ||||
|     ); | ||||
| 
 | ||||
|     // Postgres doesn't allow removing values from enums, we have to recreate the type
 | ||||
|     await queryRunner.query(`ALTER TYPE sourceType RENAME TO oldSourceType`); | ||||
|     await queryRunner.query(`CREATE TYPE sourceType AS ENUM ('machine-learning', 'exif');`); | ||||
| 
 | ||||
|     await queryRunner.query(`ALTER TABLE "asset_faces" ALTER COLUMN "sourceType" DROP DEFAULT;`); | ||||
|     await queryRunner.query( | ||||
|       `ALTER TABLE "asset_faces" ALTER COLUMN "sourceType" TYPE sourceType USING "sourceType"::text::sourceType;`, | ||||
|     ); | ||||
|     await queryRunner.query( | ||||
|       `ALTER TABLE "asset_faces" ALTER COLUMN "sourceType" SET DEFAULT 'machine-learning'::sourceType;`, | ||||
|     ); | ||||
|     await queryRunner.query(`DROP TYPE oldSourceType;`); | ||||
|   } | ||||
| } | ||||
| @ -736,6 +736,7 @@ export class PersonService extends BaseService { | ||||
|       boundingBoxX2: dto.x + dto.width, | ||||
|       boundingBoxY1: dto.y, | ||||
|       boundingBoxY2: dto.y + dto.height, | ||||
|       sourceType: dto.sourceType, | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|  | ||||
| @ -4,7 +4,7 @@ | ||||
|   import { notificationController } from '$lib/components/shared-components/notification/notification'; | ||||
|   import { isFaceEditMode } from '$lib/stores/face-edit.svelte'; | ||||
|   import { getPeopleThumbnailUrl } from '$lib/utils'; | ||||
|   import { getAllPeople, createFace, type PersonResponseDto } from '@immich/sdk'; | ||||
|   import { getAllPeople, createFace, type PersonResponseDto, SourceType } from '@immich/sdk'; | ||||
|   import { Button } from '@immich/ui'; | ||||
|   import { Canvas, InteractiveFabricObject, Rect } from 'fabric'; | ||||
|   import { onMount } from 'svelte'; | ||||
| @ -288,6 +288,7 @@ | ||||
|         assetFaceCreateDto: { | ||||
|           assetId, | ||||
|           personId: person.id, | ||||
|           sourceType: SourceType.Manual, | ||||
|           ...data, | ||||
|         }, | ||||
|       }); | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user