mirror of
https://github.com/immich-app/immich.git
synced 2024-12-24 10:37:28 +02:00
Added schedule job to perform reverse geocoding if key is added after backing up assets (#305)
This commit is contained in:
parent
e6d30d72fa
commit
357f7d1c31
@ -3,11 +3,16 @@ import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { AssetEntity } from '@app/database/entities/asset.entity';
|
||||
import { ScheduleTasksService } from './schedule-tasks.service';
|
||||
import { thumbnailGeneratorQueueName, videoConversionQueueName } from '@app/job/constants/queue-name.constant';
|
||||
import {
|
||||
metadataExtractionQueueName,
|
||||
thumbnailGeneratorQueueName,
|
||||
videoConversionQueueName,
|
||||
} from '@app/job/constants/queue-name.constant';
|
||||
import { ExifEntity } from '@app/database/entities/exif.entity';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([AssetEntity]),
|
||||
TypeOrmModule.forFeature([AssetEntity, ExifEntity]),
|
||||
BullModule.registerQueue({
|
||||
name: videoConversionQueueName,
|
||||
defaultJobOptions: {
|
||||
@ -24,6 +29,15 @@ import { thumbnailGeneratorQueueName, videoConversionQueueName } from '@app/job/
|
||||
removeOnFail: false,
|
||||
},
|
||||
}),
|
||||
|
||||
BullModule.registerQueue({
|
||||
name: metadataExtractionQueueName,
|
||||
defaultJobOptions: {
|
||||
attempts: 3,
|
||||
removeOnComplete: true,
|
||||
removeOnFail: false,
|
||||
},
|
||||
}),
|
||||
],
|
||||
providers: [ScheduleTasksService],
|
||||
})
|
||||
|
@ -1,14 +1,23 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { Cron, CronExpression } from '@nestjs/schedule';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { IsNull, Not, Repository } from 'typeorm';
|
||||
import { AssetEntity, AssetType } from '@app/database/entities/asset.entity';
|
||||
import { InjectQueue } from '@nestjs/bull';
|
||||
import { Queue } from 'bull';
|
||||
import { randomUUID } from 'crypto';
|
||||
import { generateWEBPThumbnailProcessorName, mp4ConversionProcessorName } from '@app/job/constants/job-name.constant';
|
||||
import { thumbnailGeneratorQueueName, videoConversionQueueName } from '@app/job/constants/queue-name.constant';
|
||||
import { IVideoTranscodeJob } from '@app/job/interfaces/video-transcode.interface';
|
||||
import { ExifEntity } from '@app/database/entities/exif.entity';
|
||||
import {
|
||||
IMetadataExtractionJob,
|
||||
IVideoTranscodeJob,
|
||||
metadataExtractionQueueName,
|
||||
thumbnailGeneratorQueueName,
|
||||
videoConversionQueueName,
|
||||
generateWEBPThumbnailProcessorName,
|
||||
mp4ConversionProcessorName,
|
||||
reverseGeocodingProcessorName,
|
||||
} from '@app/job';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
|
||||
@Injectable()
|
||||
export class ScheduleTasksService {
|
||||
@ -16,17 +25,23 @@ export class ScheduleTasksService {
|
||||
@InjectRepository(AssetEntity)
|
||||
private assetRepository: Repository<AssetEntity>,
|
||||
|
||||
@InjectRepository(ExifEntity)
|
||||
private exifRepository: Repository<ExifEntity>,
|
||||
|
||||
@InjectQueue(thumbnailGeneratorQueueName)
|
||||
private thumbnailGeneratorQueue: Queue,
|
||||
|
||||
@InjectQueue(videoConversionQueueName)
|
||||
private videoConversionQueue: Queue<IVideoTranscodeJob>,
|
||||
|
||||
@InjectQueue(metadataExtractionQueueName)
|
||||
private metadataExtractionQueue: Queue<IMetadataExtractionJob>,
|
||||
|
||||
private configService: ConfigService,
|
||||
) {}
|
||||
|
||||
@Cron(CronExpression.EVERY_DAY_AT_MIDNIGHT)
|
||||
async webpConversion() {
|
||||
Logger.log('Starting Schedule Webp Conversion Tasks', 'CronjobWebpGenerator');
|
||||
|
||||
const assets = await this.assetRepository.find({
|
||||
where: {
|
||||
webpPath: '',
|
||||
@ -64,4 +79,23 @@ export class ScheduleTasksService {
|
||||
await this.videoConversionQueue.add(mp4ConversionProcessorName, { asset }, { jobId: randomUUID() });
|
||||
}
|
||||
}
|
||||
|
||||
@Cron(CronExpression.EVERY_5_SECONDS)
|
||||
async reverseGeocoding() {
|
||||
const isMapboxEnable = this.configService.get('ENABLE_MAPBOX');
|
||||
|
||||
if (isMapboxEnable) {
|
||||
const exifInfo = await this.exifRepository.find({
|
||||
where: {
|
||||
city: IsNull(),
|
||||
longitude: Not(IsNull()),
|
||||
latitude: Not(IsNull()),
|
||||
},
|
||||
});
|
||||
|
||||
for (const exif of exifInfo) {
|
||||
await this.metadataExtractionQueue.add(reverseGeocodingProcessorName, { exif }, { jobId: randomUUID() });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -21,6 +21,8 @@ import {
|
||||
objectDetectionProcessorName,
|
||||
videoMetadataExtractionProcessorName,
|
||||
metadataExtractionQueueName,
|
||||
reverseGeocodingProcessorName,
|
||||
IReverseGeocodingProcessor,
|
||||
} from '@app/job';
|
||||
|
||||
@Processor(metadataExtractionQueueName)
|
||||
@ -98,6 +100,28 @@ export class MetadataExtractionProcessor {
|
||||
}
|
||||
}
|
||||
|
||||
@Process({ name: reverseGeocodingProcessorName })
|
||||
async reverseGeocoding(job: Job<IReverseGeocodingProcessor>) {
|
||||
const { exif } = job.data;
|
||||
|
||||
if (this.geocodingClient) {
|
||||
const geoCodeInfo: MapiResponse = await this.geocodingClient
|
||||
.reverseGeocode({
|
||||
query: [Number(exif.longitude), Number(exif.latitude)],
|
||||
types: ['country', 'region', 'place'],
|
||||
})
|
||||
.send();
|
||||
|
||||
const res: [] = geoCodeInfo.body['features'];
|
||||
|
||||
const city = res.filter((geoInfo) => geoInfo['place_type'][0] == 'place')[0]['text'];
|
||||
const state = res.filter((geoInfo) => geoInfo['place_type'][0] == 'region')[0]['text'];
|
||||
const country = res.filter((geoInfo) => geoInfo['place_type'][0] == 'country')[0]['text'];
|
||||
|
||||
await this.exifRepository.update({ id: exif.id }, { city, state, country });
|
||||
}
|
||||
}
|
||||
|
||||
@Process({ name: imageTaggingProcessorName, concurrency: 2 })
|
||||
async tagImage(job: Job) {
|
||||
const { asset }: { asset: AssetEntity } = job.data;
|
||||
|
@ -19,5 +19,6 @@ export const generateWEBPThumbnailProcessorName = 'generate-webp-thumbnail';
|
||||
*/
|
||||
export const exifExtractionProcessorName = 'exif-extraction';
|
||||
export const videoMetadataExtractionProcessorName = 'extract-video-metadata';
|
||||
export const reverseGeocodingProcessorName = 'reverse-geocoding';
|
||||
export const objectDetectionProcessorName = 'detect-object';
|
||||
export const imageTaggingProcessorName = 'tag-image';
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { AssetEntity } from '@app/database/entities/asset.entity';
|
||||
import { ExifEntity } from '@app/database/entities/exif.entity';
|
||||
|
||||
export interface IExifExtractionProcessor {
|
||||
/**
|
||||
@ -24,4 +25,14 @@ export interface IVideoLengthExtractionProcessor {
|
||||
asset: AssetEntity;
|
||||
}
|
||||
|
||||
export type IMetadataExtractionJob = IExifExtractionProcessor | IVideoLengthExtractionProcessor;
|
||||
export interface IReverseGeocodingProcessor {
|
||||
/**
|
||||
* The Asset entity that was saved in the database
|
||||
*/
|
||||
exif: ExifEntity;
|
||||
}
|
||||
|
||||
export type IMetadataExtractionJob =
|
||||
| IExifExtractionProcessor
|
||||
| IVideoLengthExtractionProcessor
|
||||
| IReverseGeocodingProcessor;
|
||||
|
Loading…
Reference in New Issue
Block a user