refactor(server): Move metadata extraction to domain (#4243)

* use storageRepository in metadata extraction

* move metadata extraction processor to domain

* cleanup infra/domain

---------

Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>
This commit is contained in:
Daniel Dietzler
2023-09-27 20:44:51 +02:00
committed by GitHub
parent 9bada51d56
commit 3a44e8f8d3
17 changed files with 410 additions and 396 deletions

View File

@@ -6,13 +6,12 @@ import {
ICommunicationRepository,
ICryptoRepository,
IFaceRepository,
IGeocodingRepository,
IJobRepository,
IKeyRepository,
ILibraryRepository,
IMachineLearningRepository,
IMediaRepository,
immichAppConfig,
IMetadataRepository,
IPartnerRepository,
IPersonRepository,
ISearchRepository,
@@ -23,6 +22,7 @@ import {
ITagRepository,
IUserRepository,
IUserTokenRepository,
immichAppConfig,
} from '@app/domain';
import { BullModule } from '@nestjs/bullmq';
import { Global, Module, Provider } from '@nestjs/common';
@@ -33,20 +33,20 @@ import { databaseConfig } from './database.config';
import { databaseEntities } from './entities';
import { bullConfig, bullQueues } from './infra.config';
import {
APIKeyRepository,
AccessRepository,
AlbumRepository,
APIKeyRepository,
AssetRepository,
AuditRepository,
CommunicationRepository,
CryptoRepository,
FaceRepository,
FilesystemProvider,
GeocodingRepository,
JobRepository,
LibraryRepository,
MachineLearningRepository,
MediaRepository,
MetadataRepository,
PartnerRepository,
PersonRepository,
SharedLinkRepository,
@@ -66,11 +66,11 @@ const providers: Provider[] = [
{ provide: ICommunicationRepository, useClass: CommunicationRepository },
{ provide: ICryptoRepository, useClass: CryptoRepository },
{ provide: IFaceRepository, useClass: FaceRepository },
{ provide: IGeocodingRepository, useClass: GeocodingRepository },
{ provide: IJobRepository, useClass: JobRepository },
{ provide: ILibraryRepository, useClass: LibraryRepository },
{ provide: IKeyRepository, useClass: APIKeyRepository },
{ provide: IMachineLearningRepository, useClass: MachineLearningRepository },
{ provide: IMetadataRepository, useClass: MetadataRepository },
{ provide: IPartnerRepository, useClass: PartnerRepository },
{ provide: IPersonRepository, useClass: PersonRepository },
{ provide: ISearchRepository, useClass: TypesenseRepository },

View File

@@ -8,7 +8,7 @@ import {
} from '@app/domain';
import archiver from 'archiver';
import { constants, createReadStream, existsSync, mkdirSync } from 'fs';
import fs, { readdir } from 'fs/promises';
import fs, { readdir, writeFile } from 'fs/promises';
import { glob } from 'glob';
import mv from 'mv';
import { promisify } from 'node:util';
@@ -39,6 +39,18 @@ export class FilesystemProvider implements IStorageRepository {
};
}
async readFile(filepath: string, options?: fs.FileReadOptions<Buffer>): Promise<Buffer> {
const file = await fs.open(filepath);
try {
const { buffer } = await file.read(options);
return buffer;
} finally {
await file.close();
}
}
writeFile = writeFile;
async moveFile(source: string, destination: string): Promise<void> {
if (await this.checkFileExists(destination)) {
throw new Error(`Destination file already exists: ${destination}`);

View File

@@ -7,11 +7,11 @@ export * from './communication.repository';
export * from './crypto.repository';
export * from './face.repository';
export * from './filesystem.provider';
export * from './geocoding.repository';
export * from './job.repository';
export * from './library.repository';
export * from './machine-learning.repository';
export * from './media.repository';
export * from './metadata.repository';
export * from './partner.repository';
export * from './person.repository';
export * from './shared-link.repository';

View File

@@ -1,7 +1,9 @@
import { GeoPoint, IGeocodingRepository, ReverseGeocodeResult } from '@app/domain';
import { GeoPoint, IMetadataRepository, ImmichTags, ReverseGeocodeResult } from '@app/domain';
import { REVERSE_GEOCODING_DUMP_DIRECTORY } from '@app/infra';
import { Injectable, Logger } from '@nestjs/common';
import { DefaultReadTaskOptions, exiftool } from 'exiftool-vendored';
import { readdir, rm } from 'fs/promises';
import * as geotz from 'geo-tz';
import { getName } from 'i18n-iso-countries';
import geocoder, { AddressObject, InitOptions } from 'local-reverse-geocoder';
import path from 'path';
@@ -21,8 +23,8 @@ export type GeoData = AddressObject & {
const lookup = promisify<GeoPoint[], number, AddressObject[][]>(geocoder.lookUp).bind(geocoder);
@Injectable()
export class GeocodingRepository implements IGeocodingRepository {
private logger = new Logger(GeocodingRepository.name);
export class MetadataRepository implements IMetadataRepository {
private logger = new Logger(MetadataRepository.name);
async init(options: Partial<InitOptions>): Promise<void> {
return new Promise<void>((resolve) => {
@@ -69,4 +71,22 @@ export class GeocodingRepository implements IGeocodingRepository {
return { country, state, city };
}
getExifTags(path: string): Promise<ImmichTags | null> {
return exiftool
.read<ImmichTags>(path, undefined, {
...DefaultReadTaskOptions,
defaultVideosToUTC: true,
backfillTimezones: true,
inferTimezoneFromDatestamps: true,
useMWG: true,
numericTags: DefaultReadTaskOptions.numericTags.concat(['FocalLength']),
geoTz: (lat, lon) => geotz.find(lat, lon)[0],
})
.catch((error) => {
this.logger.warn(`Error reading exif data (${path}): ${error}`, error?.stack);
return null;
});
}
}