feat(server): CLIP search integration (#1939)

This commit is contained in:
Alex 2023-03-18 08:44:42 -05:00 committed by GitHub
parent 0d436db3ea
commit f56eaae019
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
46 changed files with 673 additions and 773 deletions

View file

@ -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 () => {

View file

@ -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);
}

View file

@ -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,

View file

@ -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);

View file

@ -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;
}

View file

@ -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);
}

View file

@ -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);
}
}