From b95bc32310e4ab10c7d28db79125780f4a3affda Mon Sep 17 00:00:00 2001 From: Yashraj Jain Date: Fri, 1 Nov 2024 19:33:03 +0530 Subject: [PATCH 1/2] fix(mobile): do not removed not backup asset when selecting the correspond options (#13256) * fixed the local ids selecting issue * code: updated impl inside deleteLocalOnlyAssets * fix: used png instead of jpg to maintain picture quality * Revert "fix: used png instead of jpg to maintain picture quality" This reverts commit 04f2ed54e40b454a057f97872836d71af50b06de. * fix: update logic from code-review perspective * refractor (mobile) : Dart fix applied * fix (mobile) : Updated multi grid as per requirement --------- Co-authored-by: Alex --- mobile/lib/providers/asset.provider.dart | 22 +++++++++++++++---- .../widgets/asset_grid/multiselect_grid.dart | 14 +++++++++++- 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/mobile/lib/providers/asset.provider.dart b/mobile/lib/providers/asset.provider.dart index c7e75df79b..3855a00b76 100644 --- a/mobile/lib/providers/asset.provider.dart +++ b/mobile/lib/providers/asset.provider.dart @@ -84,34 +84,48 @@ class AssetNotifier extends StateNotifier { _deleteInProgress = true; state = true; try { + // Filter the assets based on the backed-up status final assets = onlyBackedUp ? deleteAssets.where((e) => e.storage == AssetState.merged) : deleteAssets; + + if (assets.isEmpty) { + return false; // No assets to delete + } + + // Proceed with local deletion of the filtered assets final localDeleted = await _deleteLocalAssets(assets); + if (localDeleted.isNotEmpty) { - final localOnlyIds = deleteAssets + final localOnlyIds = assets .where((e) => e.storage == AssetState.local) .map((e) => e.id) .toList(); - // Update merged assets to remote only + + // Update merged assets to remote-only final mergedAssets = - deleteAssets.where((e) => e.storage == AssetState.merged).map((e) { + assets.where((e) => e.storage == AssetState.merged).map((e) { e.localId = null; return e; }).toList(); + + // Update the local database await _db.writeTxn(() async { if (mergedAssets.isNotEmpty) { - await _db.assets.putAll(mergedAssets); + await _db.assets + .putAll(mergedAssets); // Use the filtered merged assets } await _db.exifInfos.deleteAll(localOnlyIds); await _db.assets.deleteAll(localOnlyIds); }); + return true; } } finally { _deleteInProgress = false; state = false; } + return false; } diff --git a/mobile/lib/widgets/asset_grid/multiselect_grid.dart b/mobile/lib/widgets/asset_grid/multiselect_grid.dart index eeecfa9b58..2bceafb595 100644 --- a/mobile/lib/widgets/asset_grid/multiselect_grid.dart +++ b/mobile/lib/widgets/asset_grid/multiselect_grid.dart @@ -203,18 +203,30 @@ class MultiselectGrid extends HookConsumerWidget { void onDeleteLocal(bool onlyBackedUp) async { processing.value = true; try { + // Select only the local assets from the selection final localIds = selection.value.where((a) => a.isLocal).toList(); + // Delete only the backed-up assets if 'onlyBackedUp' is true final isDeleted = await ref .read(assetProvider.notifier) .deleteLocalOnlyAssets(localIds, onlyBackedUp: onlyBackedUp); + if (isDeleted) { + // Show a toast with the correct number of deleted assets + final deletedCount = localIds + .where( + (e) => !onlyBackedUp || e.isRemote, + ) // Only count backed-up assets + .length; + ImmichToast.show( context: context, msg: 'assets_removed_permanently_from_device' - .tr(args: ["${localIds.length}"]), + .tr(args: ["$deletedCount"]), gravity: ToastGravity.BOTTOM, ); + + // Reset the selection selectionEnabledHook.value = false; } } finally { From cdabd081392fa4ff730ba21d7ef46e9b3ba44e78 Mon Sep 17 00:00:00 2001 From: Carsten Otto Date: Fri, 1 Nov 2024 15:34:34 +0100 Subject: [PATCH 2/2] 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 cc898e7468..a0c429a82e 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 3e057d2f58..99544a2004 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 3e958ad4df..194f61a0a4 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) : {};