diff --git a/e2e/src/api/specs/asset.e2e-spec.ts b/e2e/src/api/specs/asset.e2e-spec.ts index 4dd02ec69fc3d..014cea116c6f6 100644 --- a/e2e/src/api/specs/asset.e2e-spec.ts +++ b/e2e/src/api/specs/asset.e2e-spec.ts @@ -1148,6 +1148,54 @@ 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', + }, + }, + }, ]; it(`should upload and generate a thumbnail for different file types`, async () => { diff --git a/e2e/test-assets b/e2e/test-assets index 3e057d2f58750..c2d5f290a2e67 160000 --- a/e2e/test-assets +++ b/e2e/test-assets @@ -1 +1 @@ -Subproject commit 3e057d2f58750acdf7ff281a3938e34a86cfef4d +Subproject commit c2d5f290a2e671fa2106fa8b7146991be27c0515 diff --git a/server/src/services/metadata.service.ts b/server/src/services/metadata.service.ts index a81d1b4904c27..9538130ee0626 100644 --- a/server/src/services/metadata.service.ts +++ b/server/src/services/metadata.service.ts @@ -192,6 +192,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, @@ -209,8 +211,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), @@ -334,6 +336,29 @@ 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 = Number.NaN; + let height = Number.NaN; + const imageSize = exifTags.ImageSize; + if (imageSize && imageSize.indexOf('x') > 0) { + // ImageSize is "width x height" (e.g. "100x200") + const split = imageSize.split('x'); + width = Number.parseInt(split[0]); + height = Number.parseInt(split[1]); + } + if (!width || !height) { + width = exifTags.ImageWidth ?? Number.NaN; + height = exifTags.ImageHeight ?? Number.NaN; + } + 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) : {};