diff --git a/mobile/openapi/lib/model/asset_face_create_dto.dart b/mobile/openapi/lib/model/asset_face_create_dto.dart index 29e8244a9..d25a5d8b8 100644 Binary files a/mobile/openapi/lib/model/asset_face_create_dto.dart and b/mobile/openapi/lib/model/asset_face_create_dto.dart differ diff --git a/mobile/openapi/lib/model/source_type.dart b/mobile/openapi/lib/model/source_type.dart index 13c450b01..4da5aba49 100644 Binary files a/mobile/openapi/lib/model/source_type.dart and b/mobile/openapi/lib/model/source_type.dart differ diff --git a/open-api/immich-openapi-specs.json b/open-api/immich-openapi-specs.json index 6a5700108..aeafc27ee 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 7786c09d9..7237e0aac 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 4a2adc917..bc88d7de3 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 0778c35b8..c4d3018be 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 7bf4ca3dc..676e1d27d 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 000000000..dd53312ad --- /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 dd998cc0f..62bf55a78 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 afe45331e..bcc9ee687 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, }, });