From 0886281dd8e4729580dd8d446666f2500e5fa2d7 Mon Sep 17 00:00:00 2001 From: Brandon Wees Date: Tue, 10 Feb 2026 10:30:34 -0600 Subject: [PATCH] fix: create face exif orientation handling (#26108) * fix: handle exif orientation when creating face * chore: tests --- server/src/services/person.service.ts | 5 +- .../specs/services/person.service.spec.ts | 70 +++++++++++++++++++ 2 files changed, 73 insertions(+), 2 deletions(-) diff --git a/server/src/services/person.service.ts b/server/src/services/person.service.ts index 52a4e6048f..e63dcedb7d 100644 --- a/server/src/services/person.service.ts +++ b/server/src/services/person.service.ts @@ -677,8 +677,9 @@ export class PersonService extends BaseService { }; // now coordinates are in original image space - dto.imageHeight = asset.exifInfo.exifImageHeight; - dto.imageWidth = asset.exifInfo.exifImageWidth; + const originalDimensions = getDimensions(asset.exifInfo); + dto.imageWidth = originalDimensions.width; + dto.imageHeight = originalDimensions.height; } await this.personRepository.createAssetFace({ diff --git a/server/test/medium/specs/services/person.service.spec.ts b/server/test/medium/specs/services/person.service.spec.ts index a13f64032c..c86bd96a10 100644 --- a/server/test/medium/specs/services/person.service.spec.ts +++ b/server/test/medium/specs/services/person.service.spec.ts @@ -685,5 +685,75 @@ describe(PersonService.name, () => { ]), ); }); + + it('should properly handle exif orientation when creating a face on an edited asset', async () => { + const { sut, ctx } = setup(); + const { user } = await ctx.newUser(); + const { person } = await ctx.newPerson({ ownerId: user.id }); + const { asset } = await ctx.newAsset({ id: factory.uuid(), ownerId: user.id, width: 100, height: 100 }); + await ctx.newExif({ assetId: asset.id, exifImageHeight: 200, exifImageWidth: 100, orientation: '6' }); + + await ctx.newEdits(asset.id, { + edits: [ + { + action: AssetEditAction.Mirror, + parameters: { + axis: MirrorAxis.Horizontal, + }, + }, + { + action: AssetEditAction.Mirror, + parameters: { + axis: MirrorAxis.Vertical, + }, + }, + ], + }); + + const auth = factory.auth({ user }); + + const dto: AssetFaceCreateDto = { + imageWidth: 100, + imageHeight: 100, + x: 10, + y: 10, + width: 80, + height: 80, + personId: person.id, + assetId: asset.id, + }; + + await sut.createFace(auth, dto); + + const faces = sut.getFacesById(auth, { id: asset.id }); + await expect(faces).resolves.toHaveLength(1); + await expect(faces).resolves.toEqual( + expect.arrayContaining([ + expect.objectContaining({ + person: expect.objectContaining({ id: person.id }), + boundingBoxX1: 110, + boundingBoxY1: 10, + boundingBoxX2: 190, + boundingBoxY2: 90, + }), + ]), + ); + + // remove edits and verify the stored coordinates map to the original image + await ctx.newEdits(asset.id, { edits: [] }); + const facesAfterRemovingEdits = sut.getFacesById(auth, { id: asset.id }); + + await expect(facesAfterRemovingEdits).resolves.toEqual( + expect.arrayContaining([ + expect.objectContaining({ + person: expect.objectContaining({ id: person.id }), + boundingBoxX1: 10, + boundingBoxY1: 10, + boundingBoxX2: 90, + boundingBoxY2: 90, + }), + ]), + ); + }); }); });