1
0
Fork 0
mirror of https://github.com/immich-app/immich.git synced 2025-04-21 15:36:26 +02:00

fix(server): skip stacked assets in duplicate detection ()

* skip stacked assets in duplicate detection

* update sql

* handle stacking after duplicate detection runs
This commit is contained in:
Mert 2025-02-27 19:16:13 +03:00 committed by GitHub
parent a808b8610e
commit a708649504
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 34 additions and 0 deletions

View file

@ -0,0 +1,14 @@
import { MigrationInterface, QueryRunner } from 'typeorm';
export class UnsetStackedAssetsFromDuplicateStatus1740654480319 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`
update assets
set "duplicateId" = null
where "stackId" is not null`);
}
public async down(): Promise<void> {
// No need to revert this migration
}
}

View file

@ -333,6 +333,7 @@ with
and "assets"."duplicateId" is not null
and "assets"."deletedAt" is null
and "assets"."isVisible" = $2
and "assets"."stackId" is null
group by
"assets"."duplicateId"
),

View file

@ -112,6 +112,7 @@ with
and "assets"."isVisible" = $3
and "assets"."type" = $4
and "assets"."id" != $5::uuid
and "assets"."stackId" is null
order by
smart_search.embedding <=> $6
limit

View file

@ -794,6 +794,7 @@ export class AssetRepository {
.where('assets.duplicateId', 'is not', null)
.where('assets.deletedAt', 'is', null)
.where('assets.isVisible', '=', true)
.where('assets.stackId', 'is', null)
.groupBy('assets.duplicateId'),
)
.with('unique', (qb) =>

View file

@ -318,6 +318,7 @@ export class SearchRepository {
.where('assets.isVisible', '=', true)
.where('assets.type', '=', type)
.where('assets.id', '!=', asUuid(assetId))
.where('assets.stackId', 'is', null)
.orderBy(sql`smart_search.embedding <=> ${embedding}`)
.limit(64),
)

View file

@ -173,6 +173,16 @@ describe(SearchService.name, () => {
expect(mocks.logger.error).toHaveBeenCalledWith(`Asset ${assetStub.image.id} not found`);
});
it('should skip if asset is part of stack', async () => {
const id = assetStub.primaryImage.id;
mocks.asset.getById.mockResolvedValue(assetStub.primaryImage);
const result = await sut.handleSearchDuplicates({ id });
expect(result).toBe(JobStatus.SKIPPED);
expect(mocks.logger.debug).toHaveBeenCalledWith(`Asset ${id} is part of a stack, skipping`);
});
it('should skip if asset is not visible', async () => {
const id = assetStub.livePhotoMotionAsset.id;
mocks.asset.getById.mockResolvedValue(assetStub.livePhotoMotionAsset);

View file

@ -59,6 +59,11 @@ export class DuplicateService extends BaseService {
return JobStatus.FAILED;
}
if (asset.stackId) {
this.logger.debug(`Asset ${id} is part of a stack, skipping`);
return JobStatus.SKIPPED;
}
if (!asset.isVisible) {
this.logger.debug(`Asset ${id} is not visible, skipping`);
return JobStatus.SKIPPED;

View file

@ -184,6 +184,7 @@ export const assetStub = {
exifImageHeight: 1000,
exifImageWidth: 1000,
} as ExifEntity,
stackId: 'stack-1',
stack: stackStub('stack-1', [
{ id: 'primary-asset-id' } as AssetEntity,
{ id: 'stack-child-asset-1' } as AssetEntity,