mirror of
https://github.com/immich-app/immich.git
synced 2026-05-18 03:10:24 +03:00
feat: create new person in face editor (#27364)
* feat: create new person in face editor * add delay * fix: test * i18n * fix: unit test * pr feedback
This commit is contained in:
@@ -12,7 +12,13 @@ import { PersonFactory } from 'test/factories/person.factory';
|
||||
import { UserFactory } from 'test/factories/user.factory';
|
||||
import { authStub } from 'test/fixtures/auth.stub';
|
||||
import { systemConfigStub } from 'test/fixtures/system-config.stub';
|
||||
import { getAsDetectedFace, getForAssetFace, getForDetectedFaces, getForFacialRecognitionJob } from 'test/mappers';
|
||||
import {
|
||||
getAsDetectedFace,
|
||||
getForAsset,
|
||||
getForAssetFace,
|
||||
getForDetectedFaces,
|
||||
getForFacialRecognitionJob,
|
||||
} from 'test/mappers';
|
||||
import { newDate, newUuid } from 'test/small.factory';
|
||||
import { makeStream, newTestService, ServiceMocks } from 'test/utils';
|
||||
|
||||
@@ -370,6 +376,86 @@ describe(PersonService.name, () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('createFace', () => {
|
||||
it('should create a manual face and initialize the person feature photo creation', async () => {
|
||||
const auth = AuthFactory.create();
|
||||
const asset = AssetFactory.create();
|
||||
const person = PersonFactory.create({ faceAssetId: null });
|
||||
const featureFace = AssetFaceFactory.create({
|
||||
assetId: asset.id,
|
||||
personId: person.id,
|
||||
sourceType: SourceType.Manual,
|
||||
});
|
||||
|
||||
mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([asset.id]));
|
||||
mocks.access.person.checkOwnerAccess.mockResolvedValue(new Set([person.id]));
|
||||
mocks.asset.getById.mockResolvedValue(getForAsset(asset));
|
||||
mocks.person.getById.mockResolvedValue(person);
|
||||
mocks.person.getRandomFace.mockResolvedValue(featureFace);
|
||||
mocks.person.update.mockResolvedValue({ ...person, faceAssetId: featureFace.id });
|
||||
|
||||
await expect(
|
||||
sut.createFace(auth, {
|
||||
assetId: asset.id,
|
||||
personId: person.id,
|
||||
imageHeight: 500,
|
||||
imageWidth: 400,
|
||||
x: 10,
|
||||
y: 20,
|
||||
width: 100,
|
||||
height: 110,
|
||||
}),
|
||||
).resolves.toBeUndefined();
|
||||
|
||||
expect(mocks.asset.getById).toHaveBeenCalledWith(asset.id, { edits: true, exifInfo: true });
|
||||
expect(mocks.person.createAssetFace).toHaveBeenCalledWith({
|
||||
assetId: asset.id,
|
||||
personId: person.id,
|
||||
imageHeight: 500,
|
||||
imageWidth: 400,
|
||||
boundingBoxX1: 10,
|
||||
boundingBoxX2: 110,
|
||||
boundingBoxY1: 20,
|
||||
boundingBoxY2: 130,
|
||||
sourceType: SourceType.Manual,
|
||||
});
|
||||
expect(mocks.person.getRandomFace).toHaveBeenCalledWith(person.id);
|
||||
expect(mocks.person.update).toHaveBeenCalledWith({ id: person.id, faceAssetId: featureFace.id });
|
||||
expect(mocks.job.queueAll).toHaveBeenCalledWith([
|
||||
{ name: JobName.PersonGenerateThumbnail, data: { id: person.id } },
|
||||
]);
|
||||
});
|
||||
|
||||
it('should not update the person feature photo if one already exists', async () => {
|
||||
const auth = AuthFactory.create();
|
||||
const asset = AssetFactory.create();
|
||||
const person = PersonFactory.create({ faceAssetId: newUuid() });
|
||||
|
||||
mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([asset.id]));
|
||||
mocks.access.person.checkOwnerAccess.mockResolvedValue(new Set([person.id]));
|
||||
mocks.asset.getById.mockResolvedValue(getForAsset(asset));
|
||||
mocks.person.getById.mockResolvedValue(person);
|
||||
|
||||
await expect(
|
||||
sut.createFace(auth, {
|
||||
assetId: asset.id,
|
||||
personId: person.id,
|
||||
imageHeight: 500,
|
||||
imageWidth: 400,
|
||||
x: 10,
|
||||
y: 20,
|
||||
width: 100,
|
||||
height: 110,
|
||||
}),
|
||||
).resolves.toBeUndefined();
|
||||
|
||||
expect(mocks.person.createAssetFace).toHaveBeenCalledOnce();
|
||||
expect(mocks.person.getRandomFace).not.toHaveBeenCalled();
|
||||
expect(mocks.person.update).not.toHaveBeenCalled();
|
||||
expect(mocks.job.queueAll).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('createNewFeaturePhoto', () => {
|
||||
it('should change person feature photo', async () => {
|
||||
const person = PersonFactory.create();
|
||||
|
||||
@@ -631,7 +631,11 @@ export class PersonService extends BaseService {
|
||||
this.requireAccess({ auth, permission: Permission.PersonRead, ids: [dto.personId] }),
|
||||
]);
|
||||
|
||||
const asset = await this.assetRepository.getById(dto.assetId, { edits: true, exifInfo: true });
|
||||
const [asset, person] = await Promise.all([
|
||||
this.assetRepository.getById(dto.assetId, { edits: true, exifInfo: true }),
|
||||
this.findOrFail(dto.personId),
|
||||
]);
|
||||
|
||||
if (!asset) {
|
||||
throw new NotFoundException('Asset not found');
|
||||
}
|
||||
@@ -689,6 +693,10 @@ export class PersonService extends BaseService {
|
||||
boundingBoxY2: Math.round(bottomRight.y),
|
||||
sourceType: SourceType.Manual,
|
||||
});
|
||||
|
||||
if (!person.faceAssetId) {
|
||||
await this.createNewFeaturePhoto([person.id]);
|
||||
}
|
||||
}
|
||||
|
||||
async deleteFace(auth: AuthDto, id: string, dto: AssetFaceDeleteDto): Promise<void> {
|
||||
|
||||
Reference in New Issue
Block a user