From c59ced62afce81327d8944a170774bc1c5335091 Mon Sep 17 00:00:00 2001 From: Carsten Otto Date: Fri, 1 Nov 2024 15:34:34 +0100 Subject: [PATCH] fix(server): wrong image dimensions for RAW files (RAF, CR2) (also fixes face preview) (#13377) --- e2e/src/api/specs/asset.e2e-spec.ts | 72 +++++++++++++++++++++++++ e2e/test-assets | 2 +- server/src/services/metadata.service.ts | 19 ++++++- 3 files changed, 90 insertions(+), 3 deletions(-) diff --git a/e2e/src/api/specs/asset.e2e-spec.ts b/e2e/src/api/specs/asset.e2e-spec.ts index cc898e7468717..a0c429a82e224 100644 --- a/e2e/src/api/specs/asset.e2e-spec.ts +++ b/e2e/src/api/specs/asset.e2e-spec.ts @@ -1148,6 +1148,78 @@ describe('/asset', () => { }, }, }, + { + input: 'formats/raw/Canon/PowerShot_G12.CR2', + expected: { + type: AssetTypeEnum.Image, + originalFileName: 'PowerShot_G12.CR2', + fileCreatedAt: '2015-12-27T09:55:40.000Z', + exifInfo: { + make: 'Canon', + model: 'Canon PowerShot G12', + exifImageHeight: 2736, + exifImageWidth: 3648, + exposureTime: '1/1000', + fNumber: 4, + focalLength: 18.098, + iso: 80, + lensModel: null, + fileSizeInByte: 11_113_617, + dateTimeOriginal: '2015-12-27T09:55:40.000Z', + latitude: null, + longitude: null, + orientation: '1', + }, + }, + }, + { + input: 'formats/raw/Fujifilm/X100V_compressed.RAF', + expected: { + type: AssetTypeEnum.Image, + originalFileName: 'X100V_compressed.RAF', + fileCreatedAt: '2024-10-12T21:01:01.000Z', + exifInfo: { + make: 'FUJIFILM', + model: 'X100V', + exifImageHeight: 4160, + exifImageWidth: 6240, + exposureTime: '1/4000', + fNumber: 16, + focalLength: 23, + iso: 160, + lensModel: null, + fileSizeInByte: 13_551_312, + dateTimeOriginal: '2024-10-12T21:01:01.000Z', + latitude: null, + longitude: null, + orientation: '6', + }, + }, + }, + { + input: 'formats/raw/Ricoh/GR3/Ricoh_GR3-450.DNG', + expected: { + type: AssetTypeEnum.Image, + originalFileName: 'Ricoh_GR3-450.DNG', + fileCreatedAt: '2024-06-08T13:48:39.000Z', + exifInfo: { + dateTimeOriginal: '2024-06-08T13:48:39.000Z', + exifImageHeight: 4064, + exifImageWidth: 6112, + exposureTime: '1/400', + fNumber: 5, + fileSizeInByte: 31_175_472, + focalLength: 18.3, + iso: 100, + latitude: 36.613_24, + lensModel: 'GR LENS 18.3mm F2.8', + longitude: -121.897_85, + make: 'RICOH IMAGING COMPANY, LTD.', + model: 'RICOH GR III', + orientation: '1', + }, + }, + }, ]; it(`should upload and generate a thumbnail for different file types`, async () => { diff --git a/e2e/test-assets b/e2e/test-assets index 3e057d2f58750..99544a200412d 160000 --- a/e2e/test-assets +++ b/e2e/test-assets @@ -1 +1 @@ -Subproject commit 3e057d2f58750acdf7ff281a3938e34a86cfef4d +Subproject commit 99544a200412d553103cc7b8f1a28f339c7cffd9 diff --git a/server/src/services/metadata.service.ts b/server/src/services/metadata.service.ts index 3e958ad4df4d6..194f61a0a4ead 100644 --- a/server/src/services/metadata.service.ts +++ b/server/src/services/metadata.service.ts @@ -187,6 +187,8 @@ export class MetadataService extends BaseService { const { dateTimeOriginal, localDateTime, timeZone, modifyDate } = this.getDates(asset, exifTags); const { latitude, longitude, country, state, city } = await this.getGeo(exifTags, reverseGeocoding); + const { width, height } = this.getImageDimensions(exifTags); + const exifData: Partial = { assetId: asset.id, @@ -204,8 +206,8 @@ export class MetadataService extends BaseService { // image/file fileSizeInByte: stats.size, - exifImageHeight: validate(exifTags.ImageHeight), - exifImageWidth: validate(exifTags.ImageWidth), + exifImageHeight: validate(height), + exifImageWidth: validate(width), orientation: validate(exifTags.Orientation)?.toString() ?? null, projectionType: exifTags.ProjectionType ? String(exifTags.ProjectionType).toUpperCase() : null, bitsPerSample: this.getBitsPerSample(exifTags), @@ -333,6 +335,19 @@ export class MetadataService extends BaseService { return JobStatus.SUCCESS; } + private getImageDimensions(exifTags: ImmichTags): { width?: number; height?: number } { + /* + * The "true" values for width and height are a bit hidden, depending on the camera model and file format. + * For RAW images in the CR2 or RAF format, the "ImageSize" value seems to be correct, + * but ImageWidth and ImageHeight are not correct (they contain the dimensions of the preview image). + */ + let [width, height] = exifTags.ImageSize?.split('x').map((dim) => Number.parseInt(dim) || undefined) || []; + if (!width || !height) { + [width, height] = [exifTags.ImageWidth, exifTags.ImageHeight]; + } + return { width, height }; + } + private async getExifTags(asset: AssetEntity): Promise { const mediaTags = await this.metadataRepository.readTags(asset.originalPath); const sidecarTags = asset.sidecarPath ? await this.metadataRepository.readTags(asset.sidecarPath) : {};