diff --git a/server/src/repositories/metadata.repository.ts b/server/src/repositories/metadata.repository.ts index 1d504f6c71..c4a3b4f89e 100644 --- a/server/src/repositories/metadata.repository.ts +++ b/server/src/repositories/metadata.repository.ts @@ -92,8 +92,18 @@ export class MetadataRepository { /* eslint unicorn/no-array-callback-reference: off, unicorn/no-array-method-this-argument: off */ geoTz: (lat, lon) => geotz.find(lat, lon)[0], geolocation: true, - // Enable exiftool LFS to parse metadata for files larger than 2GB. - readArgs: ['-api', 'largefilesupport=1', '--ICC_Profile:DeviceManufacturer', '--ICC_Profile:DeviceModelName'], + readArgs: [ + // Enable exiftool LFS to parse metadata for files larger than 2GB. + '-api', + 'largefilesupport=1', + '--ICC_Profile:DeviceManufacturer', + '--ICC_Profile:DeviceModelName', + // Ignore embedded thumbnail dimensions/orientation for the main asset. + '--IFD1:Orientation', + '--MWG:Orientation', + '--IFD1:ImageWidth', + '--IFD1:ImageHeight', + ], writeArgs: ['-api', 'largefilesupport=1', '-overwrite_original'], taskTimeoutMillis: 2 * 60 * 1000, }); diff --git a/server/test/medium/specs/services/metadata.service.spec.ts b/server/test/medium/specs/services/metadata.service.spec.ts index 37603520f7..c3f111e5aa 100644 --- a/server/test/medium/specs/services/metadata.service.spec.ts +++ b/server/test/medium/specs/services/metadata.service.spec.ts @@ -151,6 +151,59 @@ describe(MetadataService.name, () => { // note that this date is technically wrong. it does not throw though and should get the user's attention either way. ).resolves.toEqual({ dateTimeOriginal: new Date('4260-03-05T04:04:12.000Z') }); }); + + it('should ignore IFD1 thumbnail orientation when extracting metadata', async () => { + const { sut, ctx } = setup(); + ctx.getMock(EventRepository).emit.mockResolvedValue(); + const { filePath } = await createTestFile({ 'IFD1:Orientation#': 6 }); + const { user } = await ctx.newUser(); + const { asset } = await ctx.newAsset({ originalPath: filePath, ownerId: user.id }); + await ctx.newExif({ assetId: asset.id, description: '' }); + + await sut.handleMetadataExtraction({ id: asset.id }); + + await expect( + ctx.database + .selectFrom('asset_exif') + .select('orientation') + .where('assetId', '=', asset.id) + .executeTakeFirstOrThrow(), + ).resolves.toEqual({ orientation: null }); + }); + + it('should ignore IFD1 thumbnail dimensions when extracting metadata', async () => { + const { sut, ctx } = setup(); + ctx.getMock(EventRepository).emit.mockResolvedValue(); + const { filePath } = await createTestFile({ 'IFD1:ImageWidth#': 160, 'IFD1:ImageHeight#': 120 }); + const { user } = await ctx.newUser(); + const { asset } = await ctx.newAsset({ originalPath: filePath, ownerId: user.id }); + await ctx.newExif({ assetId: asset.id, description: '' }); + + await sut.handleMetadataExtraction({ id: asset.id }); + + await expect(ctx.get(AssetRepository).getById(asset.id)).resolves.toEqual( + expect.objectContaining({ width: 1, height: 1 }), + ); + }); + + it('should keep IFD0 orientation when extracting metadata', async () => { + const { sut, ctx } = setup(); + ctx.getMock(EventRepository).emit.mockResolvedValue(); + const { filePath } = await createTestFile({ 'IFD0:Orientation#': 6 }); + const { user } = await ctx.newUser(); + const { asset } = await ctx.newAsset({ originalPath: filePath, ownerId: user.id }); + await ctx.newExif({ assetId: asset.id, description: '' }); + + await sut.handleMetadataExtraction({ id: asset.id }); + + await expect( + ctx.database + .selectFrom('asset_exif') + .select('orientation') + .where('assetId', '=', asset.id) + .executeTakeFirstOrThrow(), + ).resolves.toEqual({ orientation: '6' }); + }); }); it('should handle float lens models (#30492)', async () => {