mirror of
https://github.com/immich-app/immich.git
synced 2025-01-12 15:32:36 +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 { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
import { AssetEntity } from '@app/database/entities/asset.entity';
|
import { AssetEntity } from '@app/database/entities/asset.entity';
|
||||||
import { ScheduleTasksService } from './schedule-tasks.service';
|
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({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
TypeOrmModule.forFeature([AssetEntity]),
|
TypeOrmModule.forFeature([AssetEntity, ExifEntity]),
|
||||||
BullModule.registerQueue({
|
BullModule.registerQueue({
|
||||||
name: videoConversionQueueName,
|
name: videoConversionQueueName,
|
||||||
defaultJobOptions: {
|
defaultJobOptions: {
|
||||||
@ -24,6 +29,15 @@ import { thumbnailGeneratorQueueName, videoConversionQueueName } from '@app/job/
|
|||||||
removeOnFail: false,
|
removeOnFail: false,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
BullModule.registerQueue({
|
||||||
|
name: metadataExtractionQueueName,
|
||||||
|
defaultJobOptions: {
|
||||||
|
attempts: 3,
|
||||||
|
removeOnComplete: true,
|
||||||
|
removeOnFail: false,
|
||||||
|
},
|
||||||
|
}),
|
||||||
],
|
],
|
||||||
providers: [ScheduleTasksService],
|
providers: [ScheduleTasksService],
|
||||||
})
|
})
|
||||||
|
@ -1,14 +1,23 @@
|
|||||||
import { Injectable, Logger } from '@nestjs/common';
|
import { Injectable, Logger } from '@nestjs/common';
|
||||||
import { Cron, CronExpression } from '@nestjs/schedule';
|
import { Cron, CronExpression } from '@nestjs/schedule';
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
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 { AssetEntity, AssetType } from '@app/database/entities/asset.entity';
|
||||||
import { InjectQueue } from '@nestjs/bull';
|
import { InjectQueue } from '@nestjs/bull';
|
||||||
import { Queue } from 'bull';
|
import { Queue } from 'bull';
|
||||||
import { randomUUID } from 'crypto';
|
import { randomUUID } from 'crypto';
|
||||||
import { generateWEBPThumbnailProcessorName, mp4ConversionProcessorName } from '@app/job/constants/job-name.constant';
|
import { ExifEntity } from '@app/database/entities/exif.entity';
|
||||||
import { thumbnailGeneratorQueueName, videoConversionQueueName } from '@app/job/constants/queue-name.constant';
|
import {
|
||||||
import { IVideoTranscodeJob } from '@app/job/interfaces/video-transcode.interface';
|
IMetadataExtractionJob,
|
||||||
|
IVideoTranscodeJob,
|
||||||
|
metadataExtractionQueueName,
|
||||||
|
thumbnailGeneratorQueueName,
|
||||||
|
videoConversionQueueName,
|
||||||
|
generateWEBPThumbnailProcessorName,
|
||||||
|
mp4ConversionProcessorName,
|
||||||
|
reverseGeocodingProcessorName,
|
||||||
|
} from '@app/job';
|
||||||
|
import { ConfigService } from '@nestjs/config';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ScheduleTasksService {
|
export class ScheduleTasksService {
|
||||||
@ -16,17 +25,23 @@ export class ScheduleTasksService {
|
|||||||
@InjectRepository(AssetEntity)
|
@InjectRepository(AssetEntity)
|
||||||
private assetRepository: Repository<AssetEntity>,
|
private assetRepository: Repository<AssetEntity>,
|
||||||
|
|
||||||
|
@InjectRepository(ExifEntity)
|
||||||
|
private exifRepository: Repository<ExifEntity>,
|
||||||
|
|
||||||
@InjectQueue(thumbnailGeneratorQueueName)
|
@InjectQueue(thumbnailGeneratorQueueName)
|
||||||
private thumbnailGeneratorQueue: Queue,
|
private thumbnailGeneratorQueue: Queue,
|
||||||
|
|
||||||
@InjectQueue(videoConversionQueueName)
|
@InjectQueue(videoConversionQueueName)
|
||||||
private videoConversionQueue: Queue<IVideoTranscodeJob>,
|
private videoConversionQueue: Queue<IVideoTranscodeJob>,
|
||||||
|
|
||||||
|
@InjectQueue(metadataExtractionQueueName)
|
||||||
|
private metadataExtractionQueue: Queue<IMetadataExtractionJob>,
|
||||||
|
|
||||||
|
private configService: ConfigService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@Cron(CronExpression.EVERY_DAY_AT_MIDNIGHT)
|
@Cron(CronExpression.EVERY_DAY_AT_MIDNIGHT)
|
||||||
async webpConversion() {
|
async webpConversion() {
|
||||||
Logger.log('Starting Schedule Webp Conversion Tasks', 'CronjobWebpGenerator');
|
|
||||||
|
|
||||||
const assets = await this.assetRepository.find({
|
const assets = await this.assetRepository.find({
|
||||||
where: {
|
where: {
|
||||||
webpPath: '',
|
webpPath: '',
|
||||||
@ -64,4 +79,23 @@ export class ScheduleTasksService {
|
|||||||
await this.videoConversionQueue.add(mp4ConversionProcessorName, { asset }, { jobId: randomUUID() });
|
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,
|
objectDetectionProcessorName,
|
||||||
videoMetadataExtractionProcessorName,
|
videoMetadataExtractionProcessorName,
|
||||||
metadataExtractionQueueName,
|
metadataExtractionQueueName,
|
||||||
|
reverseGeocodingProcessorName,
|
||||||
|
IReverseGeocodingProcessor,
|
||||||
} from '@app/job';
|
} from '@app/job';
|
||||||
|
|
||||||
@Processor(metadataExtractionQueueName)
|
@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 })
|
@Process({ name: imageTaggingProcessorName, concurrency: 2 })
|
||||||
async tagImage(job: Job) {
|
async tagImage(job: Job) {
|
||||||
const { asset }: { asset: AssetEntity } = job.data;
|
const { asset }: { asset: AssetEntity } = job.data;
|
||||||
|
@ -19,5 +19,6 @@ export const generateWEBPThumbnailProcessorName = 'generate-webp-thumbnail';
|
|||||||
*/
|
*/
|
||||||
export const exifExtractionProcessorName = 'exif-extraction';
|
export const exifExtractionProcessorName = 'exif-extraction';
|
||||||
export const videoMetadataExtractionProcessorName = 'extract-video-metadata';
|
export const videoMetadataExtractionProcessorName = 'extract-video-metadata';
|
||||||
|
export const reverseGeocodingProcessorName = 'reverse-geocoding';
|
||||||
export const objectDetectionProcessorName = 'detect-object';
|
export const objectDetectionProcessorName = 'detect-object';
|
||||||
export const imageTaggingProcessorName = 'tag-image';
|
export const imageTaggingProcessorName = 'tag-image';
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { AssetEntity } from '@app/database/entities/asset.entity';
|
import { AssetEntity } from '@app/database/entities/asset.entity';
|
||||||
|
import { ExifEntity } from '@app/database/entities/exif.entity';
|
||||||
|
|
||||||
export interface IExifExtractionProcessor {
|
export interface IExifExtractionProcessor {
|
||||||
/**
|
/**
|
||||||
@ -24,4 +25,14 @@ export interface IVideoLengthExtractionProcessor {
|
|||||||
asset: AssetEntity;
|
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