mirror of https://github.com/immich-app/immich.git
deDuplicateAll and keepAll endpoints
parent
2330062ad5
commit
c9b4012abe
@ -0,0 +1,38 @@
|
||||
import { AssetResponseDto } from "src/dtos/asset-response.dto";
|
||||
import { suggestDuplicate } from "src/utils/duplicate-utils";
|
||||
|
||||
|
||||
describe('choosing a duplicate', () => {
|
||||
it('picks the asset with the largest file size', () => {
|
||||
const assets = [
|
||||
{ exifInfo: { fileSizeInByte: 300 } },
|
||||
{ exifInfo: { fileSizeInByte: 200 } },
|
||||
{ exifInfo: { fileSizeInByte: 100 } },
|
||||
];
|
||||
expect(suggestDuplicate(assets as AssetResponseDto[])).toEqual(assets[0]);
|
||||
});
|
||||
|
||||
it('picks the asset with the most exif data if multiple assets have the same file size', () => {
|
||||
const assets = [
|
||||
{ exifInfo: { fileSizeInByte: 200, rating: 5, fNumber: 1 } },
|
||||
{ exifInfo: { fileSizeInByte: 200, rating: 5 } },
|
||||
{ exifInfo: { fileSizeInByte: 100, rating: 5 } },
|
||||
];
|
||||
expect(suggestDuplicate(assets as AssetResponseDto[])).toEqual(assets[0]);
|
||||
});
|
||||
|
||||
it('returns undefined for an empty array', () => {
|
||||
const assets: AssetResponseDto[] = [];
|
||||
expect(suggestDuplicate(assets)).toBeUndefined();
|
||||
});
|
||||
|
||||
it('handles assets with no exifInfo', () => {
|
||||
const assets = [{ exifInfo: { fileSizeInByte: 200 } }, {}];
|
||||
expect(suggestDuplicate(assets as AssetResponseDto[])).toEqual(assets[0]);
|
||||
});
|
||||
|
||||
it('handles assets with exifInfo but no fileSizeInByte', () => {
|
||||
const assets = [{ exifInfo: { rating: 5, fNumber: 1 } }, { exifInfo: { rating: 5 } }];
|
||||
expect(suggestDuplicate(assets as AssetResponseDto[])).toEqual(assets[0]);
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,30 @@
|
||||
import { sortBy } from "lodash";
|
||||
import { AssetResponseDto } from "src/dtos/asset-response.dto";
|
||||
import { getExifCount } from "src/utils/exif-utils";
|
||||
|
||||
/**
|
||||
* Suggests the best duplicate asset to keep from a list of duplicates.
|
||||
*
|
||||
* The best asset is determined by the following criteria:
|
||||
* - Largest image file size in bytes
|
||||
* - Largest count of exif data
|
||||
*
|
||||
* @param assets List of duplicate assets
|
||||
* @returns The best asset to keepweb/src/lib/utils/duplicate-utils.spec.ts
|
||||
*/
|
||||
export const suggestDuplicate = (assets: AssetResponseDto[]): AssetResponseDto | undefined => {
|
||||
let duplicateAssets = sortBy(assets, (asset) => asset.exifInfo?.fileSizeInByte ?? 0);
|
||||
|
||||
// Update the list to only include assets with the largest file size
|
||||
duplicateAssets = duplicateAssets.filter(
|
||||
(asset) => asset.exifInfo?.fileSizeInByte === duplicateAssets.at(-1)?.exifInfo?.fileSizeInByte,
|
||||
);
|
||||
|
||||
// If there are multiple assets with the same file size, sort the list by the count of exif data
|
||||
if (duplicateAssets.length >= 2) {
|
||||
duplicateAssets = sortBy(duplicateAssets, getExifCount);
|
||||
}
|
||||
|
||||
// Return the last asset in the list
|
||||
return duplicateAssets.pop();
|
||||
};
|
||||
@ -0,0 +1,30 @@
|
||||
import { AssetResponseDto } from "src/dtos/asset-response.dto";
|
||||
import { getExifCount } from "src/utils/exif-utils";
|
||||
|
||||
|
||||
describe('getting the exif count', () => {
|
||||
it('returns 0 when exifInfo is undefined', () => {
|
||||
const asset = {};
|
||||
expect(getExifCount(asset as AssetResponseDto)).toBe(0);
|
||||
});
|
||||
|
||||
it('returns 0 when exifInfo is empty', () => {
|
||||
const asset = { exifInfo: {} };
|
||||
expect(getExifCount(asset as AssetResponseDto)).toBe(0);
|
||||
});
|
||||
|
||||
it('returns the correct count of non-null exifInfo properties', () => {
|
||||
const asset = { exifInfo: { fileSizeInByte: 200, rating: 5, fNumber: null } };
|
||||
expect(getExifCount(asset as AssetResponseDto)).toBe(2);
|
||||
});
|
||||
|
||||
it('ignores null, undefined and empty properties in exifInfo', () => {
|
||||
const asset = { exifInfo: { fileSizeInByte: 200, rating: null, fNumber: undefined, description: '' } };
|
||||
expect(getExifCount(asset as AssetResponseDto)).toBe(1);
|
||||
});
|
||||
|
||||
it('returns the correct count when all exifInfo properties are non-null', () => {
|
||||
const asset = { exifInfo: { fileSizeInByte: 200, rating: 5, fNumber: 1, description: 'test' } };
|
||||
expect(getExifCount(asset as AssetResponseDto)).toBe(4);
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,6 @@
|
||||
import { AssetResponseDto } from "src/dtos/asset-response.dto";
|
||||
|
||||
|
||||
export const getExifCount = (asset: AssetResponseDto) => {
|
||||
return Object.values(asset.exifInfo ?? {}).filter(Boolean).length;
|
||||
};
|
||||
Loading…
Reference in New Issue