Files
immich/server/apps/microservices/src/processors/thumbnail.processor.ts
Alex a45d6fdf57 Fix server crash on bad file operation and other optimizations (#291)
* Fixed issue with generating thumbnail for video with 0 length cause undefined file and crash the server
* Added all file error handling operation
* Temporarily disabled WebSocket on the web because receiving a new upload event doesn't put the new file in the correct place. 
* Cosmetic fixed on the info panel
2022-07-01 12:00:12 -05:00

108 lines
4.1 KiB
TypeScript

import { InjectQueue, Process, Processor } from '@nestjs/bull';
import { Job, Queue } from 'bull';
import { AssetEntity, AssetType } from '@app/database/entities/asset.entity';
import { Repository } from 'typeorm/repository/Repository';
import { InjectRepository } from '@nestjs/typeorm';
import sharp from 'sharp';
import { existsSync, mkdirSync } from 'node:fs';
import { randomUUID } from 'node:crypto';
import { CommunicationGateway } from '../../../immich/src/api-v1/communication/communication.gateway';
import ffmpeg from 'fluent-ffmpeg';
import { Logger } from '@nestjs/common';
@Processor('thumbnail-generator-queue')
export class ThumbnailGeneratorProcessor {
constructor(
@InjectRepository(AssetEntity)
private assetRepository: Repository<AssetEntity>,
@InjectQueue('thumbnail-generator-queue')
private thumbnailGeneratorQueue: Queue,
private wsCommunicateionGateway: CommunicationGateway,
@InjectQueue('metadata-extraction-queue')
private metadataExtractionQueue: Queue,
) {}
@Process('generate-jpeg-thumbnail')
async generateJPEGThumbnail(job: Job) {
const { asset }: { asset: AssetEntity } = job.data;
const resizePath = `upload/${asset.userId}/thumb/${asset.deviceId}/`;
if (!existsSync(resizePath)) {
mkdirSync(resizePath, { recursive: true });
}
const temp = asset.originalPath.split('/');
const originalFilename = temp[temp.length - 1].split('.')[0];
const jpegThumbnailPath = resizePath + originalFilename + '.jpeg';
if (asset.type == AssetType.IMAGE) {
sharp(asset.originalPath)
.resize(1440, 2560, { fit: 'inside' })
.jpeg()
.toFile(jpegThumbnailPath, async (err) => {
if (!err) {
await this.assetRepository.update({ id: asset.id }, { resizePath: jpegThumbnailPath });
// Update resize path to send to generate webp queue
asset.resizePath = jpegThumbnailPath;
await this.thumbnailGeneratorQueue.add('generate-webp-thumbnail', { asset }, { jobId: randomUUID() });
await this.metadataExtractionQueue.add('tag-image', { asset }, { jobId: randomUUID() });
await this.metadataExtractionQueue.add('detect-object', { asset }, { jobId: randomUUID() });
this.wsCommunicateionGateway.server.to(asset.userId).emit('on_upload_success', JSON.stringify(asset));
}
});
}
if (asset.type == AssetType.VIDEO) {
ffmpeg(asset.originalPath)
.outputOptions(['-ss 00:00:00.000', '-frames:v 1'])
.output(jpegThumbnailPath)
.on('start', () => {
Logger.log('Start Generating Video Thumbnail', 'generateJPEGThumbnail');
})
.on('error', (error) => {
Logger.error(`Cannot Generate Video Thumbnail ${error}`, 'generateJPEGThumbnail');
// reject();
})
.on('end', async () => {
Logger.log(`Generating Video Thumbnail Success ${asset.id}`, 'generateJPEGThumbnail');
await this.assetRepository.update({ id: asset.id }, { resizePath: jpegThumbnailPath });
// Update resize path to send to generate webp queue
asset.resizePath = jpegThumbnailPath;
await this.thumbnailGeneratorQueue.add('generate-webp-thumbnail', { asset }, { jobId: randomUUID() });
await this.metadataExtractionQueue.add('tag-image', { asset }, { jobId: randomUUID() });
await this.metadataExtractionQueue.add('detect-object', { asset }, { jobId: randomUUID() });
this.wsCommunicateionGateway.server.to(asset.userId).emit('on_upload_success', JSON.stringify(asset));
})
.run();
}
}
@Process({ name: 'generate-webp-thumbnail', concurrency: 2 })
async generateWepbThumbnail(job: Job<{ asset: AssetEntity }>) {
const { asset } = job.data;
if (!asset.resizePath) {
return;
}
const webpPath = asset.resizePath.replace('jpeg', 'webp');
sharp(asset.resizePath)
.resize(250)
.webp()
.toFile(webpPath, (err) => {
if (!err) {
this.assetRepository.update({ id: asset.id }, { webpPath: webpPath });
}
});
}
}