mirror of
https://github.com/KevinMidboe/immich.git
synced 2025-10-29 17:40:28 +00:00
refactor(server): jobs and processors (#1787)
* refactor: jobs and processors * refactor: storage migration processor * fix: tests * fix: code warning * chore: ignore coverage from infra * fix: sync move asset logic between job core and asset core * refactor: move error handling inside of catch * refactor(server): job core into dedicated service calls * refactor: smart info * fix: tests * chore: smart info tests * refactor: use asset repository * refactor: thumbnail processor * chore: coverage reqs
This commit is contained in:
99
server/libs/domain/src/media/media.service.ts
Normal file
99
server/libs/domain/src/media/media.service.ts
Normal file
@@ -0,0 +1,99 @@
|
||||
import { APP_UPLOAD_LOCATION } from '@app/common';
|
||||
import { AssetType } from '@app/infra/db/entities';
|
||||
import { Inject, Injectable, Logger } from '@nestjs/common';
|
||||
import { join } from 'path';
|
||||
import sanitize from 'sanitize-filename';
|
||||
import { IAssetRepository, mapAsset } from '../asset';
|
||||
import { CommunicationEvent, ICommunicationRepository } from '../communication';
|
||||
import { IAssetJob, IJobRepository, JobName } from '../job';
|
||||
import { IStorageRepository } from '../storage';
|
||||
import { IMediaRepository } from './media.repository';
|
||||
|
||||
@Injectable()
|
||||
export class MediaService {
|
||||
private logger = new Logger(MediaService.name);
|
||||
|
||||
constructor(
|
||||
@Inject(IAssetRepository) private assetRepository: IAssetRepository,
|
||||
@Inject(ICommunicationRepository) private communicationRepository: ICommunicationRepository,
|
||||
@Inject(IJobRepository) private jobRepository: IJobRepository,
|
||||
@Inject(IMediaRepository) private mediaRepository: IMediaRepository,
|
||||
@Inject(IStorageRepository) private storageRepository: IStorageRepository,
|
||||
) {}
|
||||
|
||||
async handleGenerateJpegThumbnail(data: IAssetJob): Promise<void> {
|
||||
const { asset } = data;
|
||||
|
||||
const basePath = APP_UPLOAD_LOCATION;
|
||||
const sanitizedDeviceId = sanitize(String(asset.deviceId));
|
||||
const resizePath = join(basePath, asset.ownerId, 'thumb', sanitizedDeviceId);
|
||||
const jpegThumbnailPath = join(resizePath, `${asset.id}.jpeg`);
|
||||
|
||||
this.storageRepository.mkdirSync(resizePath);
|
||||
|
||||
if (asset.type == AssetType.IMAGE) {
|
||||
try {
|
||||
await this.mediaRepository
|
||||
.resize(asset.originalPath, jpegThumbnailPath, { size: 1440, format: 'jpeg' })
|
||||
.catch(() => {
|
||||
this.logger.warn(
|
||||
'Failed to generate jpeg thumbnail for asset: ' +
|
||||
asset.id +
|
||||
' using sharp, failing over to exiftool-vendored',
|
||||
);
|
||||
return this.mediaRepository.extractThumbnailFromExif(asset.originalPath, jpegThumbnailPath);
|
||||
});
|
||||
await this.assetRepository.save({ id: asset.id, resizePath: jpegThumbnailPath });
|
||||
} catch (error: any) {
|
||||
this.logger.error('Failed to generate jpeg thumbnail for asset: ' + asset.id, error.stack);
|
||||
}
|
||||
|
||||
// Update resize path to send to generate webp queue
|
||||
asset.resizePath = jpegThumbnailPath;
|
||||
|
||||
await this.jobRepository.queue({ name: JobName.GENERATE_WEBP_THUMBNAIL, data: { asset } });
|
||||
await this.jobRepository.queue({ name: JobName.IMAGE_TAGGING, data: { asset } });
|
||||
await this.jobRepository.queue({ name: JobName.OBJECT_DETECTION, data: { asset } });
|
||||
|
||||
this.communicationRepository.send(CommunicationEvent.UPLOAD_SUCCESS, asset.ownerId, mapAsset(asset));
|
||||
}
|
||||
|
||||
if (asset.type == AssetType.VIDEO) {
|
||||
try {
|
||||
this.logger.log('Start Generating Video Thumbnail');
|
||||
await this.mediaRepository.extractVideoThumbnail(asset.originalPath, jpegThumbnailPath);
|
||||
this.logger.log(`Generating Video Thumbnail Success ${asset.id}`);
|
||||
|
||||
await this.assetRepository.save({ id: asset.id, resizePath: jpegThumbnailPath });
|
||||
|
||||
// Update resize path to send to generate webp queue
|
||||
asset.resizePath = jpegThumbnailPath;
|
||||
|
||||
await this.jobRepository.queue({ name: JobName.GENERATE_WEBP_THUMBNAIL, data: { asset } });
|
||||
await this.jobRepository.queue({ name: JobName.IMAGE_TAGGING, data: { asset } });
|
||||
await this.jobRepository.queue({ name: JobName.OBJECT_DETECTION, data: { asset } });
|
||||
|
||||
this.communicationRepository.send(CommunicationEvent.UPLOAD_SUCCESS, asset.ownerId, mapAsset(asset));
|
||||
} catch (error: any) {
|
||||
this.logger.error(`Cannot Generate Video Thumbnail: ${asset.id}`, error?.stack);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async handleGenerateWepbThumbnail(data: IAssetJob): Promise<void> {
|
||||
const { asset } = data;
|
||||
|
||||
if (!asset.resizePath) {
|
||||
return;
|
||||
}
|
||||
|
||||
const webpPath = asset.resizePath.replace('jpeg', 'webp');
|
||||
|
||||
try {
|
||||
await this.mediaRepository.resize(asset.resizePath, webpPath, { size: 250, format: 'webp' });
|
||||
await this.assetRepository.save({ id: asset.id, webpPath: webpPath });
|
||||
} catch (error: any) {
|
||||
this.logger.error('Failed to generate webp thumbnail for asset: ' + asset.id, error.stack);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user