mirror of
https://github.com/immich-app/immich
synced 2025-11-07 17:27:20 +00:00
feat(server): CLIP search integration (#1939)
This commit is contained in:
parent
0d436db3ea
commit
f56eaae019
46 changed files with 673 additions and 773 deletions
|
|
@ -163,7 +163,7 @@ describe('Album service', () => {
|
|||
|
||||
expect(result.id).toEqual(albumEntity.id);
|
||||
expect(result.albumName).toEqual(albumEntity.albumName);
|
||||
expect(jobMock.queue).toHaveBeenCalledWith({ name: JobName.SEARCH_INDEX_ALBUM, data: { album: albumEntity } });
|
||||
expect(jobMock.queue).toHaveBeenCalledWith({ name: JobName.SEARCH_INDEX_ALBUM, data: { ids: [albumEntity.id] } });
|
||||
});
|
||||
|
||||
it('gets list of albums for auth user', async () => {
|
||||
|
|
@ -316,7 +316,7 @@ describe('Album service', () => {
|
|||
albumName: updatedAlbumName,
|
||||
albumThumbnailAssetId: updatedAlbumThumbnailAssetId,
|
||||
});
|
||||
expect(jobMock.queue).toHaveBeenCalledWith({ name: JobName.SEARCH_INDEX_ALBUM, data: { album: updatedAlbum } });
|
||||
expect(jobMock.queue).toHaveBeenCalledWith({ name: JobName.SEARCH_INDEX_ALBUM, data: { ids: [updatedAlbum.id] } });
|
||||
});
|
||||
|
||||
it('prevents updating a not owned album (shared with auth user)', async () => {
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ export class AlbumService {
|
|||
|
||||
async create(authUser: AuthUserDto, createAlbumDto: CreateAlbumDto): Promise<AlbumResponseDto> {
|
||||
const albumEntity = await this.albumRepository.create(authUser.id, createAlbumDto);
|
||||
await this.jobRepository.queue({ name: JobName.SEARCH_INDEX_ALBUM, data: { album: albumEntity } });
|
||||
await this.jobRepository.queue({ name: JobName.SEARCH_INDEX_ALBUM, data: { ids: [albumEntity.id] } });
|
||||
return mapAlbum(albumEntity);
|
||||
}
|
||||
|
||||
|
|
@ -107,7 +107,7 @@ export class AlbumService {
|
|||
}
|
||||
|
||||
await this.albumRepository.delete(album);
|
||||
await this.jobRepository.queue({ name: JobName.SEARCH_REMOVE_ALBUM, data: { id: albumId } });
|
||||
await this.jobRepository.queue({ name: JobName.SEARCH_REMOVE_ALBUM, data: { ids: [albumId] } });
|
||||
}
|
||||
|
||||
async removeUserFromAlbum(authUser: AuthUserDto, albumId: string, userId: string | 'me'): Promise<void> {
|
||||
|
|
@ -171,7 +171,7 @@ export class AlbumService {
|
|||
|
||||
const updatedAlbum = await this.albumRepository.updateAlbum(album, updateAlbumDto);
|
||||
|
||||
await this.jobRepository.queue({ name: JobName.SEARCH_INDEX_ALBUM, data: { album: updatedAlbum } });
|
||||
await this.jobRepository.queue({ name: JobName.SEARCH_INDEX_ALBUM, data: { ids: [updatedAlbum.id] } });
|
||||
|
||||
return mapAlbum(updatedAlbum);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -455,8 +455,8 @@ describe('AssetService', () => {
|
|||
]);
|
||||
|
||||
expect(jobMock.queue.mock.calls).toEqual([
|
||||
[{ name: JobName.SEARCH_REMOVE_ASSET, data: { id: 'asset1' } }],
|
||||
[{ name: JobName.SEARCH_REMOVE_ASSET, data: { id: 'asset2' } }],
|
||||
[{ name: JobName.SEARCH_REMOVE_ASSET, data: { ids: ['asset1'] } }],
|
||||
[{ name: JobName.SEARCH_REMOVE_ASSET, data: { ids: ['asset2'] } }],
|
||||
[
|
||||
{
|
||||
name: JobName.DELETE_FILES,
|
||||
|
|
|
|||
|
|
@ -170,7 +170,7 @@ export class AssetService {
|
|||
|
||||
const updatedAsset = await this._assetRepository.update(authUser.id, asset, dto);
|
||||
|
||||
await this.jobRepository.queue({ name: JobName.SEARCH_INDEX_ASSET, data: { asset: updatedAsset } });
|
||||
await this.jobRepository.queue({ name: JobName.SEARCH_INDEX_ASSET, data: { ids: [assetId] } });
|
||||
|
||||
return mapAsset(updatedAsset);
|
||||
}
|
||||
|
|
@ -251,8 +251,8 @@ export class AssetService {
|
|||
res.header('Cache-Control', 'none');
|
||||
Logger.error(`Cannot create read stream for asset ${asset.id}`, 'getAssetThumbnail');
|
||||
throw new InternalServerErrorException(
|
||||
e,
|
||||
`Cannot read thumbnail file for asset ${asset.id} - contact your administrator`,
|
||||
{ cause: e as Error },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -427,7 +427,7 @@ export class AssetService {
|
|||
|
||||
try {
|
||||
await this._assetRepository.remove(asset);
|
||||
await this.jobRepository.queue({ name: JobName.SEARCH_REMOVE_ASSET, data: { id } });
|
||||
await this.jobRepository.queue({ name: JobName.SEARCH_REMOVE_ASSET, data: { ids: [id] } });
|
||||
|
||||
result.push({ id, status: DeleteAssetStatusEnum.SUCCESS });
|
||||
deleteQueue.push(asset.originalPath, asset.webpPath, asset.resizePath, asset.encodedVideoPath);
|
||||
|
|
|
|||
|
|
@ -70,6 +70,7 @@ export class JobService {
|
|||
for (const asset of assets) {
|
||||
await this.jobRepository.queue({ name: JobName.IMAGE_TAGGING, data: { asset } });
|
||||
await this.jobRepository.queue({ name: JobName.OBJECT_DETECTION, data: { asset } });
|
||||
await this.jobRepository.queue({ name: JobName.ENCODE_CLIP, data: { asset } });
|
||||
}
|
||||
return assets.length;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ export class SearchController {
|
|||
@Get()
|
||||
async search(
|
||||
@GetAuthUser() authUser: AuthUserDto,
|
||||
@Query(new ValidationPipe({ transform: true })) dto: SearchDto,
|
||||
@Query(new ValidationPipe({ transform: true })) dto: SearchDto | any,
|
||||
): Promise<SearchResponseDto> {
|
||||
return this.searchService.search(authUser, dto);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,9 @@
|
|||
import {
|
||||
AssetService,
|
||||
IAlbumJob,
|
||||
IAssetJob,
|
||||
IAssetUploadedJob,
|
||||
IBulkEntityJob,
|
||||
IDeleteFilesJob,
|
||||
IDeleteJob,
|
||||
IUserDeletionJob,
|
||||
JobName,
|
||||
MediaService,
|
||||
|
|
@ -53,15 +52,20 @@ export class BackgroundTaskProcessor {
|
|||
export class MachineLearningProcessor {
|
||||
constructor(private smartInfoService: SmartInfoService) {}
|
||||
|
||||
@Process({ name: JobName.IMAGE_TAGGING, concurrency: 2 })
|
||||
@Process({ name: JobName.IMAGE_TAGGING, concurrency: 1 })
|
||||
async onTagImage(job: Job<IAssetJob>) {
|
||||
await this.smartInfoService.handleTagImage(job.data);
|
||||
}
|
||||
|
||||
@Process({ name: JobName.OBJECT_DETECTION, concurrency: 2 })
|
||||
@Process({ name: JobName.OBJECT_DETECTION, concurrency: 1 })
|
||||
async onDetectObject(job: Job<IAssetJob>) {
|
||||
await this.smartInfoService.handleDetectObjects(job.data);
|
||||
}
|
||||
|
||||
@Process({ name: JobName.ENCODE_CLIP, concurrency: 1 })
|
||||
async onEncodeClip(job: Job<IAssetJob>) {
|
||||
await this.smartInfoService.handleEncodeClip(job.data);
|
||||
}
|
||||
}
|
||||
|
||||
@Processor(QueueName.SEARCH)
|
||||
|
|
@ -79,23 +83,23 @@ export class SearchIndexProcessor {
|
|||
}
|
||||
|
||||
@Process(JobName.SEARCH_INDEX_ALBUM)
|
||||
async onIndexAlbum(job: Job<IAlbumJob>) {
|
||||
await this.searchService.handleIndexAlbum(job.data);
|
||||
onIndexAlbum(job: Job<IBulkEntityJob>) {
|
||||
this.searchService.handleIndexAlbum(job.data);
|
||||
}
|
||||
|
||||
@Process(JobName.SEARCH_INDEX_ASSET)
|
||||
async onIndexAsset(job: Job<IAssetJob>) {
|
||||
await this.searchService.handleIndexAsset(job.data);
|
||||
onIndexAsset(job: Job<IBulkEntityJob>) {
|
||||
this.searchService.handleIndexAsset(job.data);
|
||||
}
|
||||
|
||||
@Process(JobName.SEARCH_REMOVE_ALBUM)
|
||||
async onRemoveAlbum(job: Job<IDeleteJob>) {
|
||||
await this.searchService.handleRemoveAlbum(job.data);
|
||||
onRemoveAlbum(job: Job<IBulkEntityJob>) {
|
||||
this.searchService.handleRemoveAlbum(job.data);
|
||||
}
|
||||
|
||||
@Process(JobName.SEARCH_REMOVE_ASSET)
|
||||
async onRemoveAsset(job: Job<IDeleteJob>) {
|
||||
await this.searchService.handleRemoveAsset(job.data);
|
||||
onRemoveAsset(job: Job<IBulkEntityJob>) {
|
||||
this.searchService.handleRemoveAsset(job.data);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue