mirror of
https://github.com/immich-app/immich.git
synced 2025-05-24 01:12:58 -04:00
Merge branch 'main' into main
This commit is contained in:
commit
c30ef4dfd6
@ -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 () => {
|
it(`should upload and generate a thumbnail for different file types`, async () => {
|
||||||
|
@ -1 +1 @@
|
|||||||
Subproject commit 3e057d2f58750acdf7ff281a3938e34a86cfef4d
|
Subproject commit 99544a200412d553103cc7b8f1a28f339c7cffd9
|
@ -84,34 +84,48 @@ class AssetNotifier extends StateNotifier<bool> {
|
|||||||
_deleteInProgress = true;
|
_deleteInProgress = true;
|
||||||
state = true;
|
state = true;
|
||||||
try {
|
try {
|
||||||
|
// Filter the assets based on the backed-up status
|
||||||
final assets = onlyBackedUp
|
final assets = onlyBackedUp
|
||||||
? deleteAssets.where((e) => e.storage == AssetState.merged)
|
? deleteAssets.where((e) => e.storage == AssetState.merged)
|
||||||
: deleteAssets;
|
: deleteAssets;
|
||||||
|
|
||||||
|
if (assets.isEmpty) {
|
||||||
|
return false; // No assets to delete
|
||||||
|
}
|
||||||
|
|
||||||
|
// Proceed with local deletion of the filtered assets
|
||||||
final localDeleted = await _deleteLocalAssets(assets);
|
final localDeleted = await _deleteLocalAssets(assets);
|
||||||
|
|
||||||
if (localDeleted.isNotEmpty) {
|
if (localDeleted.isNotEmpty) {
|
||||||
final localOnlyIds = deleteAssets
|
final localOnlyIds = assets
|
||||||
.where((e) => e.storage == AssetState.local)
|
.where((e) => e.storage == AssetState.local)
|
||||||
.map((e) => e.id)
|
.map((e) => e.id)
|
||||||
.toList();
|
.toList();
|
||||||
// Update merged assets to remote only
|
|
||||||
|
// Update merged assets to remote-only
|
||||||
final mergedAssets =
|
final mergedAssets =
|
||||||
deleteAssets.where((e) => e.storage == AssetState.merged).map((e) {
|
assets.where((e) => e.storage == AssetState.merged).map((e) {
|
||||||
e.localId = null;
|
e.localId = null;
|
||||||
return e;
|
return e;
|
||||||
}).toList();
|
}).toList();
|
||||||
|
|
||||||
|
// Update the local database
|
||||||
await _db.writeTxn(() async {
|
await _db.writeTxn(() async {
|
||||||
if (mergedAssets.isNotEmpty) {
|
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.exifInfos.deleteAll(localOnlyIds);
|
||||||
await _db.assets.deleteAll(localOnlyIds);
|
await _db.assets.deleteAll(localOnlyIds);
|
||||||
});
|
});
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
_deleteInProgress = false;
|
_deleteInProgress = false;
|
||||||
state = false;
|
state = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -203,18 +203,30 @@ class MultiselectGrid extends HookConsumerWidget {
|
|||||||
void onDeleteLocal(bool onlyBackedUp) async {
|
void onDeleteLocal(bool onlyBackedUp) async {
|
||||||
processing.value = true;
|
processing.value = true;
|
||||||
try {
|
try {
|
||||||
|
// Select only the local assets from the selection
|
||||||
final localIds = selection.value.where((a) => a.isLocal).toList();
|
final localIds = selection.value.where((a) => a.isLocal).toList();
|
||||||
|
|
||||||
|
// Delete only the backed-up assets if 'onlyBackedUp' is true
|
||||||
final isDeleted = await ref
|
final isDeleted = await ref
|
||||||
.read(assetProvider.notifier)
|
.read(assetProvider.notifier)
|
||||||
.deleteLocalOnlyAssets(localIds, onlyBackedUp: onlyBackedUp);
|
.deleteLocalOnlyAssets(localIds, onlyBackedUp: onlyBackedUp);
|
||||||
|
|
||||||
if (isDeleted) {
|
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(
|
ImmichToast.show(
|
||||||
context: context,
|
context: context,
|
||||||
msg: 'assets_removed_permanently_from_device'
|
msg: 'assets_removed_permanently_from_device'
|
||||||
.tr(args: ["${localIds.length}"]),
|
.tr(args: ["$deletedCount"]),
|
||||||
gravity: ToastGravity.BOTTOM,
|
gravity: ToastGravity.BOTTOM,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Reset the selection
|
||||||
selectionEnabledHook.value = false;
|
selectionEnabledHook.value = false;
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
|
@ -187,6 +187,8 @@ export class MetadataService extends BaseService {
|
|||||||
const { dateTimeOriginal, localDateTime, timeZone, modifyDate } = this.getDates(asset, exifTags);
|
const { dateTimeOriginal, localDateTime, timeZone, modifyDate } = this.getDates(asset, exifTags);
|
||||||
const { latitude, longitude, country, state, city } = await this.getGeo(exifTags, reverseGeocoding);
|
const { latitude, longitude, country, state, city } = await this.getGeo(exifTags, reverseGeocoding);
|
||||||
|
|
||||||
|
const { width, height } = this.getImageDimensions(exifTags);
|
||||||
|
|
||||||
const exifData: Partial<ExifEntity> = {
|
const exifData: Partial<ExifEntity> = {
|
||||||
assetId: asset.id,
|
assetId: asset.id,
|
||||||
|
|
||||||
@ -204,8 +206,8 @@ export class MetadataService extends BaseService {
|
|||||||
|
|
||||||
// image/file
|
// image/file
|
||||||
fileSizeInByte: stats.size,
|
fileSizeInByte: stats.size,
|
||||||
exifImageHeight: validate(exifTags.ImageHeight),
|
exifImageHeight: validate(height),
|
||||||
exifImageWidth: validate(exifTags.ImageWidth),
|
exifImageWidth: validate(width),
|
||||||
orientation: validate(exifTags.Orientation)?.toString() ?? null,
|
orientation: validate(exifTags.Orientation)?.toString() ?? null,
|
||||||
projectionType: exifTags.ProjectionType ? String(exifTags.ProjectionType).toUpperCase() : null,
|
projectionType: exifTags.ProjectionType ? String(exifTags.ProjectionType).toUpperCase() : null,
|
||||||
bitsPerSample: this.getBitsPerSample(exifTags),
|
bitsPerSample: this.getBitsPerSample(exifTags),
|
||||||
@ -333,6 +335,19 @@ export class MetadataService extends BaseService {
|
|||||||
return JobStatus.SUCCESS;
|
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<ImmichTags> {
|
private async getExifTags(asset: AssetEntity): Promise<ImmichTags> {
|
||||||
const mediaTags = await this.metadataRepository.readTags(asset.originalPath);
|
const mediaTags = await this.metadataRepository.readTags(asset.originalPath);
|
||||||
const sidecarTags = asset.sidecarPath ? await this.metadataRepository.readTags(asset.sidecarPath) : {};
|
const sidecarTags = asset.sidecarPath ? await this.metadataRepository.readTags(asset.sidecarPath) : {};
|
||||||
|
Loading…
x
Reference in New Issue
Block a user