diff --git a/server/src/domain/metadata/metadata.service.spec.ts b/server/src/domain/metadata/metadata.service.spec.ts index b3e3ec9270..2700f0080f 100644 --- a/server/src/domain/metadata/metadata.service.spec.ts +++ b/server/src/domain/metadata/metadata.service.spec.ts @@ -304,6 +304,18 @@ describe(MetadataService.name, () => { }); }); + it('should discard latitude and longitude on null island', async () => { + assetMock.getByIds.mockResolvedValue([assetStub.withLocation]); + metadataMock.readTags.mockResolvedValue({ + GPSLatitude: 0, + GPSLongitude: 0, + }); + + await sut.handleMetadataExtraction({ id: assetStub.image.id }); + expect(assetMock.getByIds).toHaveBeenCalledWith([assetStub.image.id]); + expect(assetMock.upsertExif).toHaveBeenCalledWith(expect.objectContaining({ latitude: null, longitude: null })); + }); + it('should not apply motion photos if asset is video', async () => { assetMock.getByIds.mockResolvedValue([{ ...assetStub.livePhotoMotionAsset, isVisible: true }]); mediaMock.probe.mockResolvedValue(probeStub.matroskaContainer); diff --git a/server/src/domain/metadata/metadata.service.ts b/server/src/domain/metadata/metadata.service.ts index 8b6cff4ece..e40bfead66 100644 --- a/server/src/domain/metadata/metadata.service.ts +++ b/server/src/domain/metadata/metadata.service.ts @@ -432,35 +432,40 @@ export class MetadataService { this.logger.verbose('Exif Tags', tags); - return { - exifData: { - // altitude: tags.GPSAltitude ?? null, - assetId: asset.id, - bitsPerSample: this.getBitsPerSample(tags), - colorspace: tags.ColorSpace ?? null, - dateTimeOriginal: this.getDateTimeOriginal(tags) ?? asset.fileCreatedAt, - exifImageHeight: validate(tags.ImageHeight), - exifImageWidth: validate(tags.ImageWidth), - exposureTime: tags.ExposureTime ?? null, - fileSizeInByte: stats.size, - fNumber: validate(tags.FNumber), - focalLength: validate(tags.FocalLength), - fps: validate(tags.VideoFrameRate), - iso: validate(tags.ISO), - latitude: validate(tags.GPSLatitude), - lensModel: tags.LensModel ?? null, - livePhotoCID: (tags.ContentIdentifier || tags.MediaGroupUUID) ?? null, - longitude: validate(tags.GPSLongitude), - make: tags.Make ?? null, - model: tags.Model ?? null, - modifyDate: exifDate(tags.ModifyDate) ?? asset.fileModifiedAt, - orientation: validate(tags.Orientation)?.toString() ?? null, - profileDescription: tags.ProfileDescription || tags.ProfileName || null, - projectionType: tags.ProjectionType ? String(tags.ProjectionType).toUpperCase() : null, - timeZone: tags.tz ?? null, - }, - tags, + const exifData = { + // altitude: tags.GPSAltitude ?? null, + assetId: asset.id, + bitsPerSample: this.getBitsPerSample(tags), + colorspace: tags.ColorSpace ?? null, + dateTimeOriginal: this.getDateTimeOriginal(tags) ?? asset.fileCreatedAt, + exifImageHeight: validate(tags.ImageHeight), + exifImageWidth: validate(tags.ImageWidth), + exposureTime: tags.ExposureTime ?? null, + fileSizeInByte: stats.size, + fNumber: validate(tags.FNumber), + focalLength: validate(tags.FocalLength), + fps: validate(tags.VideoFrameRate), + iso: validate(tags.ISO), + latitude: validate(tags.GPSLatitude), + lensModel: tags.LensModel ?? null, + livePhotoCID: (tags.ContentIdentifier || tags.MediaGroupUUID) ?? null, + longitude: validate(tags.GPSLongitude), + make: tags.Make ?? null, + model: tags.Model ?? null, + modifyDate: exifDate(tags.ModifyDate) ?? asset.fileModifiedAt, + orientation: validate(tags.Orientation)?.toString() ?? null, + profileDescription: tags.ProfileDescription || tags.ProfileName || null, + projectionType: tags.ProjectionType ? String(tags.ProjectionType).toUpperCase() : null, + timeZone: tags.tz ?? null, }; + + if (exifData.latitude === 0 && exifData.longitude === 0) { + console.warn('Exif data has latitude and longitude of 0, setting to null'); + exifData.latitude = null; + exifData.longitude = null; + } + + return { exifData, tags }; } private getDateTimeOriginal(tags: ImmichTags | Tags | null) { diff --git a/server/src/infra/migrations/1702257380990-DropNullIslandLatLong.ts b/server/src/infra/migrations/1702257380990-DropNullIslandLatLong.ts new file mode 100644 index 0000000000..173b2c5950 --- /dev/null +++ b/server/src/infra/migrations/1702257380990-DropNullIslandLatLong.ts @@ -0,0 +1,14 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class DropNullIslandLatLong1702257380990 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + 'UPDATE "exif" SET latitude = NULL, longitude = NULL WHERE latitude = 0 AND longitude = 0;', + ); + } + + public async down(): Promise { + // There's no way to know which assets used to have 0/0 lat-long if we've + // already run this migration. + } +}