feat: facial recognition (#2180)

This commit is contained in:
Jason Rasmussen
2023-05-17 13:07:17 -04:00
committed by GitHub
parent 115a47d4c6
commit 93863b0629
107 changed files with 3943 additions and 133 deletions

View File

@@ -210,7 +210,15 @@ export class AssetRepository implements IAssetRepository {
where: {
id: assetId,
},
relations: ['exifInfo', 'tags', 'sharedLinks', 'smartInfo'],
relations: {
exifInfo: true,
tags: true,
sharedLinks: true,
smartInfo: true,
faces: {
person: true,
},
},
});
}
@@ -239,7 +247,14 @@ export class AssetRepository implements IAssetRepository {
}
get(id: string): Promise<AssetEntity | null> {
return this.assetRepository.findOne({ where: { id } });
return this.assetRepository.findOne({
where: { id },
relations: {
faces: {
person: true,
},
},
});
}
async create(
@@ -264,11 +279,6 @@ export class AssetRepository implements IAssetRepository {
asset.isFavorite = dto.isFavorite ?? asset.isFavorite;
asset.isArchived = dto.isArchived ?? asset.isArchived;
if (dto.tagIds) {
const tags = await this._tagRepository.getByIds(userId, dto.tagIds);
asset.tags = tags;
}
if (asset.exifInfo != null) {
asset.exifInfo.description = dto.description || '';
await this.exifRepository.save(asset.exifInfo);
@@ -280,7 +290,12 @@ export class AssetRepository implements IAssetRepository {
asset.exifInfo = exifInfo;
}
return await this.assetRepository.save(asset);
await this.assetRepository.update(asset.id, {
isFavorite: asset.isFavorite,
isArchived: asset.isArchived,
});
return asset;
}
/**

View File

@@ -38,6 +38,7 @@ export class AssetCore {
tags: [],
sharedLinks: [],
originalFileName: parse(file.originalName).name,
faces: [],
});
await this.jobRepository.queue({ name: JobName.ASSET_UPLOADED, data: { asset, fileName: file.originalName } });

View File

@@ -355,6 +355,14 @@ export class AssetService {
}
try {
if (asset.faces) {
await Promise.all(
asset.faces.map(({ assetId, personId }) =>
this.jobRepository.queue({ name: JobName.SEARCH_REMOVE_FACE, data: { assetId, personId } }),
),
);
}
await this._assetRepository.remove(asset);
await this.jobRepository.queue({ name: JobName.SEARCH_REMOVE_ASSET, data: { ids: [id] } });

View File

@@ -1,13 +1,13 @@
import { UserService } from '@app/domain';
import { JobService } from '@app/domain';
import { Injectable } from '@nestjs/common';
import { Cron, CronExpression } from '@nestjs/schedule';
@Injectable()
export class AppCronJobs {
constructor(private userService: UserService) {}
constructor(private jobService: JobService) {}
@Cron(CronExpression.EVERY_DAY_AT_11PM)
async onQueueUserDeleteCheck() {
await this.userService.handleQueueUserDelete();
@Cron(CronExpression.EVERY_DAY_AT_MIDNIGHT)
async onNightlyJob() {
await this.jobService.handleNightlyJobs();
}
}

View File

@@ -10,6 +10,7 @@ import {
AlbumController,
APIKeyController,
AuthController,
PersonController,
JobController,
OAuthController,
PartnerController,
@@ -44,6 +45,7 @@ import { AppCronJobs } from './app.cron-jobs';
SharedLinkController,
SystemConfigController,
UserController,
PersonController,
],
providers: [
//

View File

@@ -4,6 +4,7 @@ export * from './auth.controller';
export * from './job.controller';
export * from './oauth.controller';
export * from './partner.controller';
export * from './person.controller';
export * from './search.controller';
export * from './server-info.controller';
export * from './shared-link.controller';

View File

@@ -0,0 +1,57 @@
import {
AssetResponseDto,
AuthUserDto,
ImmichReadStream,
PersonResponseDto,
PersonService,
PersonUpdateDto,
} from '@app/domain';
import { Body, Controller, Get, Param, Put, StreamableFile } from '@nestjs/common';
import { ApiOkResponse, ApiTags } from '@nestjs/swagger';
import { GetAuthUser } from '../decorators/auth-user.decorator';
import { Authenticated } from '../decorators/authenticated.decorator';
import { UseValidation } from '../decorators/use-validation.decorator';
import { UUIDParamDto } from './dto/uuid-param.dto';
function asStreamableFile({ stream, type, length }: ImmichReadStream) {
return new StreamableFile(stream, { type, length });
}
@ApiTags('Person')
@Controller('person')
@Authenticated()
@UseValidation()
export class PersonController {
constructor(private service: PersonService) {}
@Get()
getAllPeople(@GetAuthUser() authUser: AuthUserDto): Promise<PersonResponseDto[]> {
return this.service.getAll(authUser);
}
@Get(':id')
getPerson(@GetAuthUser() authUser: AuthUserDto, @Param() { id }: UUIDParamDto): Promise<PersonResponseDto> {
return this.service.getById(authUser, id);
}
@Put(':id')
updatePerson(
@GetAuthUser() authUser: AuthUserDto,
@Param() { id }: UUIDParamDto,
@Body() dto: PersonUpdateDto,
): Promise<PersonResponseDto> {
return this.service.update(authUser, id, dto);
}
@Get(':id/thumbnail')
@ApiOkResponse({ content: { 'application/octet-stream': { schema: { type: 'string', format: 'binary' } } } })
getPersonThumbnail(@GetAuthUser() authUser: AuthUserDto, @Param() { id }: UUIDParamDto) {
return this.service.getThumbnail(authUser, id).then(asStreamableFile);
}
@Get(':id/assets')
getPersonAssets(@GetAuthUser() authUser: AuthUserDto, @Param() { id }: UUIDParamDto): Promise<AssetResponseDto[]> {
return this.service.getAssets(authUser, id);
}
}