diff --git a/mobile/openapi/lib/model/asset_face_create_dto.dart b/mobile/openapi/lib/model/asset_face_create_dto.dart index 29e8244a96..d25a5d8b82 100644 --- a/mobile/openapi/lib/model/asset_face_create_dto.dart +++ b/mobile/openapi/lib/model/asset_face_create_dto.dart @@ -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 toJson() { final json = {}; @@ -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(json, r'imageHeight')!, imageWidth: mapValueOfType(json, r'imageWidth')!, personId: mapValueOfType(json, r'personId')!, + sourceType: SourceType.fromJson(json[r'sourceType'])!, width: mapValueOfType(json, r'width')!, x: mapValueOfType(json, r'x')!, y: mapValueOfType(json, r'y')!, @@ -147,6 +154,7 @@ class AssetFaceCreateDto { 'imageHeight', 'imageWidth', 'personId', + 'sourceType', 'width', 'x', 'y', diff --git a/mobile/openapi/lib/model/source_type.dart b/mobile/openapi/lib/model/source_type.dart index 13c450b010..4da5aba495 100644 --- a/mobile/openapi/lib/model/source_type.dart +++ b/mobile/openapi/lib/model/source_type.dart @@ -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 = [ 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'); diff --git a/open-api/immich-openapi-specs.json b/open-api/immich-openapi-specs.json index 6a57001085..aeafc27ee6 100644 --- a/open-api/immich-openapi-specs.json +++ b/open-api/immich-openapi-specs.json @@ -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" }, diff --git a/open-api/typescript-sdk/src/fetch-client.ts b/open-api/typescript-sdk/src/fetch-client.ts index 7786c09d9a..7237e0aac3 100644 --- a/open-api/typescript-sdk/src/fetch-client.ts +++ b/open-api/typescript-sdk/src/fetch-client.ts @@ -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", diff --git a/server/src/db.d.ts b/server/src/db.d.ts index 4a2adc917f..bc88d7de3c 100644 --- a/server/src/db.d.ts +++ b/server/src/db.d.ts @@ -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; diff --git a/server/src/dtos/person.dto.ts b/server/src/dtos/person.dto.ts index 0778c35b8f..c4d3018be2 100644 --- a/server/src/dtos/person.dto.ts +++ b/server/src/dtos/person.dto.ts @@ -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 { diff --git a/server/src/enum.ts b/server/src/enum.ts index 7bf4ca3dcf..676e1d27db 100644 --- a/server/src/enum.ts +++ b/server/src/enum.ts @@ -228,6 +228,7 @@ export enum AssetStatus { export enum SourceType { MACHINE_LEARNING = 'machine-learning', EXIF = 'exif', + MANUAL = 'manual', } export enum ManualJobName { diff --git a/server/src/migrations/1740619600996-AddManualSourceType.ts b/server/src/migrations/1740619600996-AddManualSourceType.ts new file mode 100644 index 0000000000..dd53312ad7 --- /dev/null +++ b/server/src/migrations/1740619600996-AddManualSourceType.ts @@ -0,0 +1,27 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddManualSourceType1740619600996 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TYPE sourceType ADD VALUE 'manual'`); + } + + public async down(queryRunner: QueryRunner): Promise { + // 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;`); + } +} diff --git a/server/src/services/person.service.ts b/server/src/services/person.service.ts index dd998cc0fe..62bf55a78c 100644 --- a/server/src/services/person.service.ts +++ b/server/src/services/person.service.ts @@ -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, }); } diff --git a/web/src/lib/components/asset-viewer/face-editor/face-editor.svelte b/web/src/lib/components/asset-viewer/face-editor/face-editor.svelte index afe45331e4..bcc9ee6875 100644 --- a/web/src/lib/components/asset-viewer/face-editor/face-editor.svelte +++ b/web/src/lib/components/asset-viewer/face-editor/face-editor.svelte @@ -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, }, });